#!/usr/bin/perl
#--------------------------------------------------------------------------
#--------------------------------------------------------------------------
#    CGI Proxy Bocah Newbie
#--------------------------------------------------------------------------
#Script by Jmarshall
#Edited by HendraDesperado
#Powered by Bocah Newbie
#--------------------------------------------------------------------------

use strict ;
use Socket ;


use vars qw(
   $TEXT_ONLY
   $REMOVE_COOKIES  $REMOVE_SCRIPTS  $FILTER_ADS  $HIDE_REFERER
   $INSERT_ENTRY_FORM  $ALLOW_USER_CONFIG
   @ALLOWED_SERVERS  @BANNED_SERVERS  @BANNED_NETWORKS
   $NO_COOKIE_WITH_IMAGE  @ALLOWED_COOKIE_SERVERS  @BANNED_COOKIE_SERVERS
   @ALLOWED_SCRIPT_SERVERS  @BANNED_SCRIPT_SERVERS
   @BANNED_IMAGE_URL_PATTERNS  $RETURN_EMPTY_GIF
   $USER_IP_ADDRESS_TEST  $DESTINATION_SERVER_TEST
   $INSERT_HTML  $INSERT_FILE  $ANONYMIZE_INSERTION  $FORM_AFTER_INSERTION
   $INSERTION_FRAME_HEIGHT
   $RUNNING_ON_SSL_SERVER  $NOT_RUNNING_AS_NPH
   $HTTP_PROXY  $SSL_PROXY  $NO_PROXY  $PROXY_AUTH  $SSL_PROXY_AUTH
   $MINIMIZE_CACHING
   $SESSION_COOKIES_ONLY  $COOKIE_PATH_FOLLOWS_SPEC  $RESPECT_THREE_DOT_RULE
   @PROXY_GROUP
   $USER_AGENT  $USE_PASSIVE_FTP_MODE  $SHOW_FTP_WELCOME
   $PROXIFY_SCRIPTS  $ALLOW_UNPROXIFIED_SCRIPTS  $PROXIFY_COMMENTS
   $ENCODE_DECODE_BLOCK_IN_JS
   $USE_POST_ON_START  $ENCODE_URL_INPUT
   $REMOVE_TITLES  $NO_BROWSE_THROUGH_SELF  $NO_LINK_TO_START  $MAX_REQUEST_SIZE
   $QUIETLY_EXIT_PROXY_SESSION
   $OVERRIDE_SECURITY

   @SCRIPT_MIME_TYPES  @OTHER_TYPES_TO_REGISTER  @TYPES_TO_HANDLE
   $NON_TEXT_EXTENSIONS
   $PROXY_VERSION

   @MONTH  @WEEKDAY  %UN_MONTH
   @BANNED_NETWORK_ADDRS
   $USER_IP_ADDRESS_TEST_H  $DESTINATION_SERVER_TEST_H
   $RUNNING_ON_IIS
   @NO_PROXY
   $NO_CACHE_HEADERS
   @ALL_TYPES  %MIME_TYPE_ID  $SCRIPT_TYPE_REGEX  $TYPES_TO_HANDLE_REGEX
   $THIS_HOST  $ENV_SERVER_PORT  $ENV_SCRIPT_NAME  $THIS_SCRIPT_URL
   $HAS_BEGUN

   $CUSTOM_INSERTION  %IN_CUSTOM_INSERTION

   $RE_JS_WHITE_SPACE  $RE_JS_LINE_TERMINATOR  $RE_JS_COMMENT
   $RE_JS_IDENTIFIER_START  $RE_JS_IDENTIFIER_PART  $RE_JS_IDENTIFIER_NAME
   $RE_JS_PUNCTUATOR  $RE_JS_DIV_PUNCTUATOR
   $RE_JS_NUMERIC_LITERAL  $RE_JS_ESCAPE_SEQUENCE  $RE_JS_STRING_LITERAL
   $RE_JS_REGULAR_EXPRESSION_LITERAL
   $RE_JS_TOKEN  $RE_JS_INPUT_ELEMENT_DIV  $RE_JS_INPUT_ELEMENT_REG_EXP
   $RE_JS_SKIP  $RE_JS_SKIP_NO_LT
   $JSLIB_BODY

   $HTTP_VERSION  $HTTP_1_X
   $URL
   $now
   $packed_flags  $encoded_URL  $doing_insert_here  $env_accept
   $e_remove_cookies  $e_remove_scripts  $e_filter_ads  $e_insert_entry_form
   $e_hide_referer
   $images_are_banned_here  $scripts_are_banned_here  $cookies_are_banned_here
   $scheme  $authority  $path  $host  $port  $username  $password
   $cookie_to_server  %auth
   $script_url  $url_start  $url_start_inframe  $url_start_noframe
   $is_in_frame  $expected_type
   $base_url  $base_scheme  $base_host  $base_path  $base_file  $base_unframes
   $default_style_type  $default_script_type
   $status  $headers  $body  $is_html  $response_sent
   %in_mini_start_form
   $needs_jslib
   $debug ) ;

unless ($HAS_BEGUN) {
$TEXT_ONLY= 0 ;      # set to 1 to allow only text data, 0 to allow all
$REMOVE_COOKIES= 0 ;
$REMOVE_SCRIPTS= 1 ;
$FILTER_ADS= 0 ;
$HIDE_REFERER= 1 ;
$INSERT_ENTRY_FORM= 1 ;
$ALLOW_USER_CONFIG= 1 ;
sub proxy_encode {
    my($URL)= @_ ;
    $URL=~ s#^([\w+.-]+)://#$1/# ;                 # http://xxx -> http/xxx
#    $URL=~ s/(.)/ sprintf('%02x',ord($1)) /ge ;   # each char -> 2-hex
#    $URL=~ tr/a-zA-Z/n-za-mN-ZA-M/ ;              # rot-13

    return $URL ;
}

sub proxy_decode {
    my($enc_URL)= @_ ;

#    $enc_URL=~ tr/a-zA-Z/n-za-mN-ZA-M/ ;        # rot-13
#    $enc_URL=~ s/([\da-fA-F]{2})/ sprintf("%c",hex($1)) /ge ;
    $enc_URL=~ s#^([\w+.-]+)/#$1://# ;           # http/xxx -> http://xxx
    return $enc_URL ;
}


sub cookie_encode {
    my($cookie)= @_ ;
#    $cookie=~ s/(.)/ sprintf('%02x',ord($1)) /ge ;   # each char -> 2-hex
#    $cookie=~ tr/a-zA-Z/n-za-mN-ZA-M/ ;              # rot-13
    $cookie=~ s/(\W)/ '%' . sprintf('%02x',ord($1)) /ge ; # simple URL-encoding
    return $cookie ;
}

sub cookie_decode {
    my($enc_cookie)= @_ ;
    $enc_cookie=~ s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;  # URL-decode
#    $enc_cookie=~ tr/a-zA-Z/n-za-mN-ZA-M/ ;          # rot-13
#    $enc_cookie=~ s/([\da-fA-F]{2})/ sprintf("%c",hex($1)) /ge ;
    return $enc_cookie ;
}


#   "_proxy_jslib_", as below.
#   set to the whole thing.]
# jsm-- String.charCodeAt not in MSIE 5.0.
# jsm-- String.replace() with function doesn't work in MSIE 5.0.

$ENCODE_DECODE_BLOCK_IN_JS= <<'EOB' ;

function _proxy_jslib_proxy_encode(URL) {
    URL= URL.replace(/^([\w\+\.\-]+)\:\/\//, '$1/') ;
//    URL= URL.replace(/(.)/g, function (s,p1) { return p1.charCodeAt(0).toString(16) } ) ;
//    URL= URL.replace(/([a-mA-M])|[n-zN-Z]/g, function (s,p1) { return String.fromCharCode(s.charCodeAt(0)+(p1?13:-13)) }) ;

    return URL ;
}

function _proxy_jslib_proxy_decode(enc_URL) {
//    enc_URL= enc_URL.replace(/([a-mA-M])|[n-zN-Z]/g, function (s,p1) { return String.fromCharCode(s.charCodeAt(0)+(p1?13:-13)) }) ;
//    enc_URL= enc_URL.replace(/([\da-fA-F]{2})/g, function (s,p1) { return String.fromCharCode(eval('0x'+p1)) } ) ;
    enc_URL= enc_URL.replace(/^([\w\+\.\-]+)\//, '$1://') ;
    return enc_URL ;
}

function _proxy_jslib_cookie_encode(cookie) {
//    cookie= cookie.replace(/(.)/g, function (s,p1) { return p1.charCodeAt(0).toString(16) } ) ;
//    cookie= cookie.replace(/([a-mA-M])|[n-zN-Z]/g, function (s,p1) { return String.fromCharCode(s.charCodeAt(0)+(p1!=null?13:-13)) }) ;
    cookie= cookie.replace(/(\W)/g, function (s,p1) { return '%'+p1.charCodeAt(0).toString(16) } ) ;
    return cookie ;
}

function _proxy_jslib_cookie_decode(enc_cookie) {
    enc_cookie= enc_cookie.replace(/%([\da-fA-F]{2})/g, function (s,p1) { return String.fromCharCode(eval('0x'+p1)) } ) ;
//    enc_cookie= enc_cookie.replace(/([a-mA-M])|[n-zN-Z]/g, function (s,p1) { return String.fromCharCode(s.charCodeAt(0)+(p1!=null?13:-13)) }) ;
//    enc_cookie= enc_cookie.replace(/([\da-fA-F]{2})/g, function (s,p1) { return String.fromCharCode(eval('0x'+p1)) } ) ;
    return enc_cookie ;
}

EOB




#   use them.)
@ALLOWED_SERVERS= () ;
@BANNED_SERVERS= () ;



@BANNED_NETWORKS= ('127', '192.168', '172', '10',
		    '169.254', '244.0.0') ;
#@ALLOWED_COOKIE_SERVERS= ('\bslashdot\.org$') ;
@BANNED_COOKIE_SERVERS= (
    '\.doubleclick\.net$',
    '\.preferences\.com$',
    '\.imgis\.com$',
    '\.adforce\.com$',
    '\.focalink\.com$',
    '\.flycast\.com$',
    '\.go\.com$',
    '\.avenuea\.com$',
    '\.linkexchange\.com$',
    '\.pathfinder\.com$',
    '\.burstnet\.com$',
    '\btripod\.com$',
    '\bgeocities\.yahoo\.com$',
    '\.mediaplex\.com$',
    ) ;

$NO_COOKIE_WITH_IMAGE= 1 ;
@ALLOWED_SCRIPT_SERVERS= () ;
@BANNED_SCRIPT_SERVERS= () ;
@BANNED_IMAGE_URL_PATTERNS= (
    'ad\.doubleclick\.net/ad/',
    '\b[a-z](\d+)?\.doubleclick\.net(:\d*)?/',
    '\.imgis\.com\b',
    '\.adforce\.com\b',
    '\.avenuea\.com\b',
    '\.go\.com(:\d*)?/ad/',
    '\.eimg\.com\b',
    '\bexcite\.netscape\.com(:\d*)?/.*/promo/',
    '/excitenetscapepromos/',
    '\.yimg\.com(:\d*)?.*/promo/',
    '\bus\.yimg\.com/[a-z]/(\w\w)/\1',
    '\bus\.yimg\.com/[a-z]/\d-/',
    '\bpromotions\.yahoo\.com(:\d*)?/promotions/',
    '\bcnn\.com(:\d*)?/ads/',
    'ads\.msn\.com\b',
    '\blinkexchange\.com\b',
    '\badknowledge\.com\b',
    '/SmartBanner/',
    '\bdeja\.com/ads/',
    '\bimage\.pathfinder\.com/sponsors',
    'ads\.tripod\.com',
    'ar\.atwola\.com/image/',
    '\brealcities\.com/ads/',
    '\bnytimes\.com/ad[sx]/',
    '\busatoday\.com/sponsors/',
    '\busatoday\.com/RealMedia/ads/',
    '\bmsads\.net/ads/',
    '\bmediaplex\.com/ads/',
    '\batdmt\.com/[a-z]/',
    '\bview\.atdmt\.com/',
    '\bADSAdClient31\.dll\b',
    ) ;

$RETURN_EMPTY_GIF= 1 ;
$USER_IP_ADDRESS_TEST= '' ;
$DESTINATION_SERVER_TEST= '' ;
#$INSERT_HTML= "<h1>This is an inserted header</h1><hr>" ;
#$INSERT_FILE= 'insert_file_name' ;

$ANONYMIZE_INSERTION= 0 ;

$FORM_AFTER_INSERTION= 0 ;

$INSERTION_FRAME_HEIGHT= $ALLOW_USER_CONFIG   ? 80   : 50 ;

$RUNNING_ON_SSL_SERVER= '' ;

$NOT_RUNNING_AS_NPH= 1 ;

#$HTTP_PROXY= $ENV{'http_proxy'} ;
#$SSL_PROXY= 'firewall.example.com:3128' ;
#$NO_PROXY= $ENV{'no_proxy'} ;
#$PROXY_AUTH= 'Aladdin:open sesame' ;
#$SSL_PROXY_AUTH= $PROXY_AUTH ;
#@PROXY_GROUP= ('http://www.example.com/~grommit/proxy/nph-proxy.cgi',
#	        'http://www.fnord.mil/langley/bavaria/atlantis/nph-proxy.cgi',
#	        'http://www.nothinghere.gov/No/Such/Agency/nph-proxy.cgi',
#	        ) ;
$MINIMIZE_CACHING= 0 ;
$SESSION_COOKIES_ONLY= 0 ;
$COOKIE_PATH_FOLLOWS_SPEC= 0 ;
$RESPECT_THREE_DOT_RULE= 0 ;
#$USER_AGENT= 'Mozilla/4.05 [en] (X11; I; Linux 2.0.34 i586)' ;
$USE_PASSIVE_FTP_MODE= 1 ;
$SHOW_FTP_WELCOME= 1 ;
$PROXIFY_SCRIPTS= 1 ;
$ALLOW_UNPROXIFIED_SCRIPTS= 0 ;
$PROXIFY_COMMENTS= 0 ;
$USE_POST_ON_START= 1 ;
$ENCODE_URL_INPUT= 0 ;
$REMOVE_TITLES= 0 ;
$NO_BROWSE_THROUGH_SELF= 0 ;
$NO_LINK_TO_START= 0 ;
$MAX_REQUEST_SIZE= 4194304 ;  # that's 4 Meg to you and me
$QUIETLY_EXIT_PROXY_SESSION= 0 ;
$OVERRIDE_SECURITY= 1 ;
@SCRIPT_MIME_TYPES= ('application/x-javascript', 'application/x-ecmascript',
		     'application/x-vbscript',   'application/x-perlscript',
		     'application/javascript',   'application/ecmascript',
		     'text/javascript',  'text/ecmascript', 'text/jscript',
		     'text/livescript',  'text/vbscript',   'text/vbs',
		     'text/perlscript',  'text/tcl',
		     'text/x-scriptlet', 'text/scriptlet',
		     'application/hta',
		    ) ;
@OTHER_TYPES_TO_REGISTER= ('text/css') ;
@TYPES_TO_HANDLE= ('text/css',
		   'application/x-javascript', 'application/x-ecmascript',
		   'application/javascript',   'application/ecmascript',
		   'text/javascript',          'text/ecmascript',
		   'text/livescript',          'text/jscript',
		  ) ;

$NON_TEXT_EXTENSIONS=
	  'gif|jpeg|jpe|jpg|tiff|tif|png|bmp|xbm'   # images
	. '|mp2|mp3|wav|aif|aiff|au|snd'            # audios
	. '|avi|qt|mov|mpeg|mpg|mpe'                # videos
	. '|gz|Z|exe|gtar|tar|zip|sit|hqx|pdf'      # applications
	. '|ram|rm|ra|swf' ;                        # others


# $PROXY_VERSION= '2.1beta9' ;


#--------------------------------------------------------------------------
#   End of normal user configuration.
#   Now, set or adjust all globals that remain constant for all runs.
#--------------------------------------------------------------------------

# First, set various constants.

# These are used in rfc1123_date() and date_is_after().
@MONTH=   qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) ;
@WEEKDAY= qw(Sun Mon Tue Wed Thu Fri Sat Sun) ;
%UN_MONTH= map { lc($MONTH[$_]), $_ }  0..$#MONTH ;   # look up by month name

# Create the sets of regular expressions we'll need if we proxify scripts.
# So far, the only script type we proxify is JavaScript.
&set_RE_JS  if $PROXIFY_SCRIPTS ;

$ENV{SCRIPT_NAME}=~ s#^/?#/# ;
if ($ENV{SERVER_SOFTWARE}=~ /^Apache\b/i) {
    my($zero)= $0=~ m#([^/]*)$# ;
    ($ENV{SCRIPT_NAME})= $ENV{SCRIPT_NAME}=~ /^(.*?\Q$zero\E)/ if $zero ne '' ;
}
$ENV_SERVER_PORT= $ENV{SERVER_PORT} ;
$ENV_SCRIPT_NAME= $ENV{SCRIPT_NAME} ;

@BANNED_NETWORK_ADDRS= () ;
for (@BANNED_NETWORKS) {
    push(@BANNED_NETWORK_ADDRS, pack('C*', /(\d+)/g)) ;
}

@{$USER_IP_ADDRESS_TEST_H}{qw(host port path socket open)}=
	(lc($1), ($2 eq '' ? 80 : $2), $3, 'S_USERTEST', 0)
    if ($USER_IP_ADDRESS_TEST=~ m#http://([^/?:]*):?(\d*)(.*)#i) ;
@{$DESTINATION_SERVER_TEST_H}{qw(host port path socket open)}=
	(lc($1), ($2 eq '' ? 80 : $2), $3, 'S_DESTTEST', 0)
    if ($DESTINATION_SERVER_TEST=~ m#http://([^/?:]*):?(\d*)(.*)#i) ;


# If $RUNNING_ON_SSL_SERVER is '', then guess based on SERVER_PORT.
$RUNNING_ON_SSL_SERVER= ($ENV_SERVER_PORT==443) if $RUNNING_ON_SSL_SERVER eq '' ;

$RUNNING_ON_IIS= ($ENV{'SERVER_SOFTWARE'}=~ /IIS/) ;


# Create @NO_PROXY from $NO_PROXY for efficiency.
@NO_PROXY= split(/\s*,\s*/, $NO_PROXY) ;

$PROXY_AUTH=     &base64($PROXY_AUTH)      if $PROXY_AUTH=~ /:/ ;
$SSL_PROXY_AUTH= &base64($SSL_PROXY_AUTH)  if $SSL_PROXY_AUTH=~ /:/ ;

foreach (@PROXY_GROUP) { s#/$## }

$NO_CACHE_HEADERS= $MINIMIZE_CACHING
    ? "Cache-Control: no-cache\015\012Pragma: no-cache\015\012"
    : '' ;


# Canonicalize all MIME types to lowercase.
for (@SCRIPT_MIME_TYPES)        { $_= lc }
for (@OTHER_TYPES_TO_REGISTER)  { $_= lc }

# $ALL_TYPES[0] is '', so we can test e.g. "if $MIME_TYPE_ID{$id} ..." .

@ALL_TYPES= ('', @SCRIPT_MIME_TYPES, @OTHER_TYPES_TO_REGISTER) ;
&HTMLdie("Too many MIME types to register.")  if @ALL_TYPES > 64 ;
@MIME_TYPE_ID{@ALL_TYPES}=  0..$#ALL_TYPES ;


# Regex that matches a script MIME type.
$SCRIPT_TYPE_REGEX= '(' . join("|", @SCRIPT_MIME_TYPES) . ')' ;

# Regex that tells us whether we handle a given MIME type.
$TYPES_TO_HANDLE_REGEX= '(' . join("|", @TYPES_TO_HANDLE) . ')' ;

if ($ENV{'HTTP_HOST'} ne '') {
    ($THIS_HOST)= $ENV{'HTTP_HOST'}=~ m#^(?:[\w+.-]+://)?([^:/?]*)# ;
    $THIS_HOST= $ENV{'SERVER_NAME'}   if $THIS_HOST eq '' ;
} else {
    $THIS_HOST= $ENV{'SERVER_NAME'} ;
}


# Build the constant $THIS_SCRIPT_URL from environment variables.  Only include
#   SERVER_PORT if it's not 80 (or 443 for SSL).
$THIS_SCRIPT_URL= $RUNNING_ON_SSL_SERVER
	    ? 'https://' . $THIS_HOST
	      . ($ENV_SERVER_PORT==443  ? ''  : ':' . $ENV_SERVER_PORT)
	      . $ENV_SCRIPT_NAME
	    : 'http://' . $THIS_HOST
	      . ($ENV_SERVER_PORT==80   ? ''  : ':' . $ENV_SERVER_PORT)
	      . $ENV_SCRIPT_NAME ;



# End of initialization of constants.
$HAS_BEGUN= 1 ;
}  # unless ($HAS_BEGUN)

#--------------------------------------------------------------------------
#   Global constants are now set.  Now do any initialization that is
#     required for every run.
#--------------------------------------------------------------------------

#$starttime= time ;
#my($sutime,$sstime)= (times)[0,1] ;

local($|)= 1 ;


reset 'a-z' ;
$URL= '' ;     # (almost) only uppercase variable that varies from run to run

#$needs_jslib= 0 ;

$now= time ;    # for (@goodmen)

($HTTP_VERSION)= $ENV{'SERVER_PROTOCOL'}=~ m#^HTTP/(\d+\.\d+)#i ;
$HTTP_VERSION= '1.0' unless $HTTP_VERSION=~ /^1\.[01]$/ ;


$HTTP_1_X=  $NOT_RUNNING_AS_NPH   ? 'Status:'   : "HTTP/$HTTP_VERSION" ;
if ($ENV{'SERVER_SOFTWARE'}=~ m#^Apache/(\d+)\.(\d+)(?:\.(\d+))?#i) {
    if (($1<=>1 or $2<=>3 or $3<=>6) < 0) {
	$SIG{'ALRM'} = \&timeexit ;
	eval { alarm(600) } ;     # use where it works, ignore where it doesn't
    }
}

sub timeexit { $ENV{'MOD_PERL'}  ? goto EXIT  : exit 1 }

$ENV{'PATH_INFO'} =~ s/^$ENV_SCRIPT_NAME//   if $RUNNING_ON_IIS ;

if ($ENV{'PATH_INFO'}=~ / /) {
    $ENV{'PATH_INFO'} =~ s/%/%25/g ;
    $ENV{'PATH_INFO'} =~ s/ /%20/g ;
}


# Copy often-used environment vars into scalars, for efficiency
$env_accept= $ENV{'HTTP_ACCEPT'} || '*/*' ;     # may be modified later

# Extract flags and encoded URL from PATH_INFO.
($packed_flags, $encoded_URL)= $ENV{'PATH_INFO'}=~ m#/([^/]*)/?(.*)# ;
if ( $ALLOW_USER_CONFIG && ($packed_flags ne '') ) {
    ($e_remove_cookies, $e_remove_scripts, $e_filter_ads, $e_hide_referer,
     $e_insert_entry_form, $is_in_frame, $expected_type)=
	 &unpack_flags($packed_flags) ;

} else {
    # $is_in_frame is set in any case.  It indicates whether the current
    #   request will be placed in a frame.
    ($e_remove_cookies, $e_remove_scripts, $e_filter_ads, $e_hide_referer,
     $e_insert_entry_form, $is_in_frame, $expected_type)=
	 ($REMOVE_COOKIES, $REMOVE_SCRIPTS, $FILTER_ADS, $HIDE_REFERER,
	  $INSERT_ENTRY_FORM, (&unpack_flags($packed_flags))[5..6] ) ;
}

$doing_insert_here= !$is_in_frame && 
    ( $e_insert_entry_form || ($INSERT_FILE ne '') || ($INSERT_HTML ne '') ) ;


binmode STDOUT ;


#--------------------------------------------------------------------------
#    parse URL, make checks, and set various globals
#--------------------------------------------------------------------------

if (@PROXY_GROUP) {
    # srand is automatically called in Perl 5.004 and later.  It might be
    #   desirable to seed based on the URL, so that multiple requests for
    #   the same URL go through the same proxy, and may thus be cached.
    #srand( unpack('%32L*', $ENV{'PATH_INFO'}) ) ;  # seed with URL+flags
    $script_url= $PROXY_GROUP[ rand(scalar @PROXY_GROUP) ] ;
} else {
    $script_url= $THIS_SCRIPT_URL ;
}

# Create $url_start and any needed variants: "$script_url/flags/"
$url_start_inframe= $script_url . '/' .
    &pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
		$e_hide_referer, $e_insert_entry_form, 1, '') . '/' ;
$url_start_noframe= $script_url . '/' .
    &pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
		$e_hide_referer, $e_insert_entry_form, 0, '') . '/' ;
$url_start=  $is_in_frame   ? $url_start_inframe   : $url_start_noframe ;


# If there's no $encoded_URL, then start a browsing session.
&show_start_form() if $encoded_URL eq '' ;


# Decode the URL.
$URL= &wrap_proxy_decode($encoded_URL) ;

$URL=~ s/(\?.*)?$/?$ENV{'QUERY_STRING'}/   if $ENV{'QUERY_STRING'} ne '' ;

($scheme, $authority, $path)= ($URL=~ m#^([\w+.-]+)://([^/?]*)(.*)$#i) ;
$scheme= lc($scheme) ;
$path= "/$path" if $path!~ m#^/# ;   # if path is '' or contains only query


# Magic here-- if $URL uses special scheme "x-proxy", immediately call the
#   general-purpose xproxy() routine.
&xproxy($URL) if $scheme eq 'x-proxy' ;

if ($ENV{'HTTP_USER_AGENT'}=~ /MSIE/) {
    $is_html= 1  if $path=~ /\.html?(\?|$)/i ;
} else {
    $is_html= 1  if $path=~ /^[^?]*\.html?(\?|$)/i ;
}


# Alert the user to unsupported URL, with an intermediate page
&unsupported_warning($URL) unless ($scheme=~ /^(http|https|ftp)$/) ;
&HTMLdie('The target URL cannot contain an empty host name.')
    unless $authority=~ /^\w/ ;

if ($scheme eq 'ftp') {
    # FTP authority can be username:password@host:port, with username,
    #   password, and port all optional.
    # Embedding your username/password in a URL is NOT RECOMMENDED!  Here,
    #   the second clause should almost always be used.
    if ($authority=~ /@/) {
	($username, $password, $host, $port)=
	    $authority=~ /([^:@]*):?([^@]*)@([^:]*):?(.*)/ ;
    } else {
	($username, $password)= ('anonymous', 'not@available.com') ;
	($host, $port)= $authority=~ /^([^:]*):?(.*)$/ ;
    }

# covers HTTP, etc.
} else {
    # Unlikely occurrence of username:password@host:port, but possible.
    #   Implies HTTP Basic authentication.  Not as much a security hole as
    #   doing the same in an FTP URL, above, but still not a great idea.
    if ($authority=~ /@/) {
	($username, $password, $host, $port)=
	    $authority=~ /([^:@]*):?([^@]*)@([^:]*):?(.*)/ ;
    } else {
	($host, $port)= $authority=~ /^([^:]*):?(.*)$/ ;
    }
}

$host= lc($host) ;      # hostnames are case-insensitive
$host=~ s/\.*$//g ;     # removes trailing dots to close a potential exploit

if ($NO_BROWSE_THROUGH_SELF) {
    # Default $port's not set yet, so hack up an ad hoc version.
    my($port2)=  $port || ( $scheme eq 'https'  ? 443  : 80 ) ;
    &loop_disallowed_die($URL)
	if     ($scheme=~ /^https?/)
	    && ($host=~ /^$THIS_HOST$/i)
	    && ($port2 == $ENV_SERVER_PORT)
	    && ($path=~ /^$ENV_SCRIPT_NAME\b/) ;
}


# Die if the user's IP address is not allowed here.
if ($USER_IP_ADDRESS_TEST) {
    my($ok) ;
    if ($USER_IP_ADDRESS_TEST_H) {
	$ok= &http_get2($USER_IP_ADDRESS_TEST_H,
			$USER_IP_ADDRESS_TEST_H->{path} . $ENV{REMOTE_ADDR}) ;
    } else {
	$ok= `$USER_IP_ADDRESS_TEST $ENV{REMOTE_ADDR}` ;
    }
    &banned_user_die if $ok==0 ;
}


# Die if the target server is not allowed, according to $DESTINATION_SERVER_TEST.
if ($DESTINATION_SERVER_TEST) {
    my($ok) ;
    my($safehost)= $host ;
    if ($DESTINATION_SERVER_TEST_H) {
	$safehost=~ s/(\W)/ '%' . sprintf('%02x', ord($1)) /ge ;
	$ok= &http_get2($DESTINATION_SERVER_TEST_H,
			$DESTINATION_SERVER_TEST_H->{path} . $safehost) ;
    } else {
	$safehost=~ s/\\/\\\\/g ;
	$safehost=~ s/'/\\'/g ;
	$ok= `$DESTINATION_SERVER_TEST '$safehost'` ;
    }
    &banned_server_die($URL) if $ok==0 ;
}



# Die if the target server is not allowed, according to @ALLOWED_SERVERS and @BANNED_SERVERS.
if (@ALLOWED_SERVERS) {
    my($server_is_allowed) ;
    foreach (@ALLOWED_SERVERS) {
	$server_is_allowed= 1, last   if $host=~ /$_/ ;
    }
    &banned_server_die($URL) unless $server_is_allowed ;
}
foreach (@BANNED_SERVERS) {
    &banned_server_die($URL) if $host=~ /$_/ ;
}


# If we're filtering ads, set $images_are_banned_here appropriately.
if ($e_filter_ads) {
    foreach (@BANNED_IMAGE_URL_PATTERNS) {
	$images_are_banned_here= 1, last if $URL=~ /$_/ ;
    }
}


# Set $scripts_are_banned_here appropriately
$scripts_are_banned_here= $e_remove_scripts ;
unless ($scripts_are_banned_here) {
    if (@ALLOWED_SCRIPT_SERVERS) {
	$scripts_are_banned_here= 1 ;
	foreach (@ALLOWED_SCRIPT_SERVERS) {
	    $scripts_are_banned_here= 0, last   if $host=~ /$_/ ;
	}
    }
    unless ($scripts_are_banned_here) {
	foreach (@BANNED_SCRIPT_SERVERS) {
	    $scripts_are_banned_here= 1, last   if $host=~ /$_/ ;
	}
    }
}


# Set $cookies_are_banned_here appropriately
$cookies_are_banned_here= $e_remove_cookies ;
unless ($cookies_are_banned_here) {
    if (@ALLOWED_COOKIE_SERVERS) {
	$cookies_are_banned_here= 1 ;
	foreach (@ALLOWED_COOKIE_SERVERS) {
	    $cookies_are_banned_here= 0, last   if $host=~ /$_/ ;
	}
    }
    unless ($cookies_are_banned_here) {
	foreach (@BANNED_COOKIE_SERVERS) {
	    $cookies_are_banned_here= 1, last   if $host=~ /$_/ ;
	}
    }
}


if ($scripts_are_banned_here && $expected_type ne '') {
    &script_content_die if $expected_type=~ /^$SCRIPT_TYPE_REGEX$/io ;
}

# Exclude non-text if it's not allowed.  Err on the side of allowing too much.
if ($TEXT_ONLY) {
    # First, forbid requests for filenames with non-text-type extensions
    &non_text_die if ($path=~ /\.($NON_TEXT_EXTENSIONS)(;|\?|$)/i) ;

    # Then, filter the "Accept:" header to accept only text
    $env_accept=~ s#\*/\*#text/*#g ;    # not strictly perfect
    $env_accept= join(', ', grep(m#^text/#i, split(/\s*,\s*/, $env_accept)) ) ;
    &non_text_die unless $env_accept ne '' ;
}


if ($images_are_banned_here) {
    &skip_image unless grep(m#^(text|\*)/#i, split(/\s*,\s*/, $env_accept) ) ;
}


$base_url= $URL ;
&fix_base_vars ;   # must be called whenever $base_url is set


$default_style_type= 'text/css' ;

$default_script_type= 'application/x-javascript' ;


# Parse the cookie for real cookies and authentication information.
($cookie_to_server, %auth)=
    &parse_cookie($ENV{'HTTP_COOKIE'}, $path, $host, $scheme) ;


#--------------------------------------------------------------------------
#    Retrieve the resource into $body using the correct scheme,
#      also setting $status, $headers, and $is_html (all globals).
#      $is_html indicates whether the original resource is HTML, not
#      if a generated response is in HTML (e.g. an error message).
#    $response_sent might be set, indicating the response was sent by the
#      subroutine.  This is appropriate for streaming media, for example.
#--------------------------------------------------------------------------

if ($scheme eq 'http') {
    &http_get ;
} elsif ($scheme eq 'https') {
    &http_get ;
} elsif ($scheme eq 'ftp') {
    &ftp_get ;
}

#--------------------------------------------------------------------------
#    Modify entire response to point back through this script
#--------------------------------------------------------------------------

# If the resource is HTML (and not empty), update all URLs in all tags that
#   refer to URLs.  Plus a bunch of other stuff.

if ( $is_html  && ($body ne '') && !$response_sent ) {

    $body= &proxify_html(\$body, 1) ;

    # Change Content-Length header, since we changed the content
    $headers=~ s/^Content-Length:.*\012/
		 'Content-Length: ' . length($body) . "\015\012"/mie ;

}



#--------------------------------------------------------------------------
#    Send response back to user
#--------------------------------------------------------------------------

# If the response has not already been sent, print the status line, headers,
#   and the entire (possibly modified) resource.
# The $response_sent flag was added purely to support streaming media and
#   large files.
if (!$response_sent) {
    if ($ENV{'REQUEST_METHOD'} eq 'HEAD') {
	print $status, $headers ;
    } elsif ($is_html) {
	print $status, $headers, $body ;
	# print $debug ;   # handy for sprinkling checks throughout the code

    } else {
	print $status, $headers, $body ;
    }
}

#if ($is_html) {
#    # OK, let's time this thing
#    my($eutime,$estime)= (times)[0,1] ;
#    open(LOG,">>proxy.log") ;
#    print LOG "full times: ", $eutime-$sutime, " ", $estime-$sstime,
#        " ", time-$starttime, "  URL: $URL\n" ;
#    close(LOG) ;
#}



EXIT:

close(S) ;
untie(*S) ;
eval { alarm(0) } ;   # use eval{} to avoid failing where alarm() is missing

exit unless $ENV{'MOD_PERL'} ;    # mod_perl scripts must not exit

#   Also note that parts of this routine are represented by other JavaScript
#   subroutines, namely _proxy_jslib_proxify_comment(),
#   _proxy_jslib_proxify_script_block(), _proxy_jslib_proxify_style_block()
#   _proxy_jslib_proxify_decl_bang(), _proxy_jslib_decl_question, and
#   _proxy_jslib_proxify_element() .

sub proxify_html {
    my($body_ref, $is_full_page, $no_exit_on_frameset)= @_ ;
    my(@out, $start, $comment, $script_block, $style_block, $decl_bang, $decl_question, $tag,
       $body_pos, $html_pos, $head_pos, $first_script_pos, $out_start,
       $has_content, $in_noscript, $in_title, $title, $full_insertion) ;
    my($ua_is_MSIE)= $ENV{'HTTP_USER_AGENT'}=~ /MSIE/ ;   # used in tight loops

   
    $body_ref= \(join('', @$body_ref)) if ref($body_ref) eq 'ARRAY' ;

   
    $body_ref= \$body_ref unless ref($body_ref) ;

    
    $full_insertion= &full_insertion($URL,0)   if $is_full_page ;


    # second line was: (?:(<!--.*?--\s*> | <!--.*?> )  # order is important
    while ( $$body_ref=~ m{([^<]*)
			   (?:(<!--(?=.*?-->).*?--\s*> | <!--(?!.*?-->).*?> )
			     |(<\s*script\b.*?<\s*/script\b.*?>)
			     |(<\s*style\b.*?<\s*/style\b.*?>)
			     |(<![^>]*>?)
			     |(<\?[^>]*>?)
			     |(<[^>]*>?)
			   )?
			  }sgix )
    {

	# Above regex must be in scalar context to work, so set vars here.
	($start, $comment, $script_block, $style_block, $decl_bang, $decl_question, $tag)=
	    ($1, $2, $3, $4, $5, $6, $7) ;



	if ($tag && !$body_pos && $tag=~ m#^<\s*/\s*title\b#i) {
	    $start= ''  if $REMOVE_TITLES ;
	    $title= $start ;
	}


	# Pass the text between tags through to the output.
	push(@out, $start) ;

	# Used when there is illegal early script content (see continue block).
	$out_start= @out ;

	$has_content||= $start=~ /\S/ unless $in_noscript || $in_title ;

	if ($tag) {

	    my($tag_name, $attrs, %attr, $name, $rebuild) ;

	    # Tag and attribute names match ([A-Za-z][\w.:-]*), I believe implied
	    #   by http://www.w3.org/TR/REC-html40/types.html#type-name .
	    ($tag_name, $attrs)= $tag=~ /^<\s*(\/?\s*[A-Za-z][\w.:-]*)\s*([^>]*)/ ;
	    $tag_name=~ s#^/\s*#/# ;
	    $tag_name= lc($tag_name) ;
	    if ($tag_name eq 'noscript') {
		$in_noscript++ ;
		if ($scripts_are_banned_here) {
		    $tag=~ s/^<\s*noscript/<div/ ;
		    $tag_name= 'div' ;
		    $rebuild= 1 ;
		}
	    } elsif ($tag_name eq '/noscript') {
		$in_noscript-- if $in_noscript>0 ;
		push(@out, '</div>'), next if $scripts_are_banned_here ;
	    } elsif ($tag_name eq 'title') {
		$in_title++ ;
	    } elsif ($tag_name eq '/title') {
		$in_title-- ;
	    }

	    
	    $html_pos= @out+1  if !$html_pos && ($tag_name eq 'html') ;
	    $head_pos= @out+1  if !$head_pos && ($tag_name eq 'head') ;
	    $body_pos= @out+1  if !$body_pos && ($tag_name eq 'body') ;

	    
	    &return_frame_doc(&wrap_proxy_encode($URL), $title)
		if ($tag_name eq 'frameset') && $doing_insert_here  && !$is_in_frame
		   && !$no_exit_on_frameset ;

	    push(@out, $tag), next   if ($attrs eq '') ;


	    PARSE_ATTRS: {
#		while ($attrs=~ /([A-Za-z][\w.:-]*)\s*(?:=\s*(?:"([^">]*)"?|'([^'>]*)'?|([^'"][^\s>]*)))?/g ) {
		while ($attrs=~ /([A-Za-z][\w.:-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^'"][^\s>]*)|(['"])))?/g ) {
		    if (defined($5)) {
			# Again, next line only works in scalar context.
			$body=~ /\G([^>]*)(>?)/gc ;
			my($extra, $close)= ($1, $2) ;
			# exit loop if at end of string
			last if ($extra eq '') and ($close eq '') ;
			$attrs.= '>' . $extra ;
			$tag.=   $extra . $close ;
			redo PARSE_ATTRS ;
		    }

		    $name= lc($1) ;
		    $rebuild= 1, next if exists($attr{$name}) ; # duplicate attr
		    $attr{$name}= &HTMLunescape(defined($2) ? $2
					      : defined($3) ? $3
					      : defined($4) ? $4
					      : '' ) ;
		}
	    }


	 

	    # If so configured, remove or proxify any script elements in each tag.
	    if ($scripts_are_banned_here) {

		
		my(@remove_attrs)= grep(/^on/ || $attr{$_}=~ /&{/ , keys %attr) ;
		delete @attr{ @remove_attrs }, $rebuild=1   if @remove_attrs ;

	    } elsif ($PROXIFY_SCRIPTS) {

		# Proxify any script macros first.
		foreach (keys %attr) {
		    $attr{$_}=~ s/&{(.*?)};/
				 '&{' . &proxify_block($1, $default_script_type) . '};'
				 /sge
			&& ($rebuild= 1) ;
		}

		
		foreach (grep(/^on/, keys %attr)) {
		    $attr{$_}= &proxify_block($attr{$_}, $default_script_type) ;
		    $rebuild= 1 ;
		}
	    }


	   
	    if (defined($attr{style})) {

		
		if ($ua_is_MSIE) {
		    if ($scripts_are_banned_here) {
			delete($attr{style}), $rebuild=1
			    if $attr{style}=~ /(?:expression|function)\s*\(/i ;

		    } elsif ($PROXIFY_SCRIPTS) {
			# Proxify any strings inside "expression()" or "function()".
			$attr{style}=~ s#((?:expression|function)\s*\()([^)]*)#
					 $1 . &proxify_block($2, 'text/jscript')
					#gie
			    && ($rebuild= 1) ;
		    }
		}

		$attr{style}= &proxify_block($attr{style}, $default_style_type), $rebuild=1 ;
	    }


	    if ($tag_name eq 'a') {
		# Remove type attribute altogether.
		delete $attr{type}, $rebuild=1   if defined($attr{type});

		if (defined($attr{href})) {

		    # If needed, detect if frame state might change.
		    # Deframe if (target unframes) or (no target and base target unframes)
		    if (   ($base_unframes && !defined($attr{target}))
			 || $attr{target}=~ /^_(top|blank)$/i         )
		    {
			$attr{href}= &full_url_by_frame($attr{href},0), $rebuild=1 ;
		    } else {
			$attr{href}= &full_url($attr{href}), $rebuild=1 ;
		    }


		 
		    ## Could require $doing_insert_here here too to save a little
		    ##   time... may not keep frame state right, but wouldn't matter.
		    #my($link_unframe) ;
		    #$link_unframe=  ($base_unframes && !defined($attr{target}))
		    #              || $attr{target}=~ /^_(top|blank)$/i
		    #    if $is_in_frame ;

		    ## Use temporary copy of $url_start to call full_url() normally.
		    ## Only generate new value if is_in_frame flag has changed,
		    ##   or if type flag needs to be changed.
		    ## Verify that $attr{type} is a valid MIME type.
		    #local($url_start)= $url_start ;
		    #if ( ($attr{type} ne '') || $link_unframe ) {
		    #    ($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    #        if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;
		    #    $url_start= $script_url . '/' .
		    #        &pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
		    #                    $e_hide_referer, $e_insert_entry_form,
		    #                    $link_unframe  ? 0  : $is_in_frame,
		    #                    lc($attr{type}))
		    #        . '/' ;
		    #}

		    #$attr{href}= &full_url($attr{href}), $rebuild=1 ;
		}



	    
	    } elsif ($tag_name eq 'img' or $tag_name eq 'image') {
		$tag_name= 'img',                        $rebuild=1  if $tag_name eq 'image' ;
		$attr{src}=    &full_url($attr{src}),    $rebuild=1  if defined($attr{src}) ;
		$attr{lowsrc}= &full_url($attr{lowsrc}), $rebuild=1  if defined($attr{lowsrc}) ;
		$attr{longdesc}= &full_url($attr{longdesc}), $rebuild=1 if defined($attr{longdesc}) ;
		$attr{usemap}= &full_url($attr{usemap}), $rebuild=1  if defined($attr{usemap}) ;
		$attr{dynsrc}= &full_url($attr{dynsrc}), $rebuild=1  if defined($attr{dynsrc}) ;


	    } elsif ($tag_name eq 'body') {
		$attr{background}= &full_url($attr{background}), $rebuild=1 if defined($attr{background}) ;

	    #   $base_ vars, and in the $base_unframes flag.

	    } elsif ($tag_name eq 'base') {
		# Remember what we need to from this <base> tag.
		$base_url= $attr{href}, &fix_base_vars  if defined($attr{href}) ;
		$base_unframes= $attr{target}=~ /^_(top|blank)$/i ;

		# Then convert any href attribute normally.
		$attr{href}= &full_url($attr{href}), $rebuild=1  if defined($attr{href}) ;



	    } elsif ($tag_name eq 'frame') {
		$attr{src}=      &full_url_by_frame($attr{src},1), $rebuild=1 if defined($attr{src}) ;
		$attr{longdesc}= &full_url($attr{longdesc}),       $rebuild=1 if defined($attr{longdesc}) ;

	    } elsif ($tag_name eq 'iframe') {
		$attr{src}=      &full_url_by_frame($attr{src},1), $rebuild=1 if defined($attr{src}) ;
		$attr{longdesc}= &full_url($attr{longdesc}),       $rebuild=1 if defined($attr{longdesc}) ;


	    # <head>'s profile attribute can be a space-separated list of URIs.
	    } elsif ($tag_name eq 'head') {
		$attr{profile}= join(' ', map {&full_url($_)} split(" ", $attr{profile})),
		    $rebuild=1  if defined($attr{profile}) ;

	    } elsif ($tag_name eq 'layer') {
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;



	    } elsif ($tag_name eq 'input') {
		$attr{src}=    &full_url($attr{src}),    $rebuild=1  if defined($attr{src}) ;
		$attr{usemap}= &full_url($attr{usemap}), $rebuild=1  if defined($attr{usemap}) ;


	    # <form> tag needs special attention, here and elsewhere.
	    # is <form script='...'> attribute ever used, or even recognized
	    #    by any browser?  It's not defined in any W3C DTD.
	    } elsif ($tag_name eq 'form') {
		# Deframe if (target unframes) or (no target and base target unframes)
		if (   ($base_unframes && !defined($attr{target}))
		     || $attr{target}=~ /^_(top|blank)$/i         )
		{
		    $attr{action}= &full_url_by_frame($attr{action},0), $rebuild=1 if defined($attr{action}) ;
		} else {
		    $attr{action}= &full_url($attr{action}),            $rebuild=1 if defined($attr{action}) ;
		}

		if ($scripts_are_banned_here) {
		    delete($attr{script}), $rebuild=1 if defined($attr{script}) ;
		} else {
		    $attr{script}= &full_url($attr{script}), $rebuild=1  if defined($attr{script}) ;
		}
	    } elsif ($tag_name eq 'area') {
		# Deframe if (target unframes) or (no target and base target unframes)
		if (   ($base_unframes && !defined($attr{target}))
		     || $attr{target}=~ /^_(top|blank)$/i         )
		{
		    $attr{href}= &full_url_by_frame($attr{href},0), $rebuild=1  if defined($attr{href}) ;
		} else {
		    $attr{href}= &full_url($attr{href}), $rebuild=1  if defined($attr{href}) ;
		}
	    } elsif ($tag_name eq 'link') {
		# Verify that $attr{type} is a valid MIME type.
		($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;

		my($type)= lc($attr{type}) ;

		
		$type= 'text/css' if ($type eq '') && $attr{rel}=~ /\bstylesheet\b/i ;

		# Remove tag if it links to a script type and scripts are banned.
		next if $scripts_are_banned_here && $type=~ /^$SCRIPT_TYPE_REGEX$/io ;

		# Deframe if (target unframes) or (no target and base target unframes)
		my($link_unframe) ;
		$link_unframe=  ($base_unframes && !defined($attr{target}))
			      || $attr{target}=~ /^_(top|blank)$/i
		    if $is_in_frame ;

		# Use temporary copy of $url_start to call full_url() normally.
		# Only generate new value if type flag has changed or we're deframing.
		local($url_start)= $url_start ;
		if ($type ne '') {
		    $url_start= $script_url . '/' .
			&pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
				    $e_hide_referer, $e_insert_entry_form,
				    $link_unframe  ? 0  : $is_in_frame,
				    $type)
			. '/' ;
		} elsif ($link_unframe) {
		    $url_start= $url_start_noframe ;
		}

		$attr{href}= &full_url($attr{href}), $rebuild=1  if defined($attr{href}) ;
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;   # Netscape?
		$attr{urn}=  &full_url($attr{urn}),  $rebuild=1  if defined($attr{urn}) ;

	    } elsif ($tag_name eq 'meta') {
		$attr{url}= &full_url($attr{url}), $rebuild=1  if defined($attr{url}) ;   # Netscape

		if (defined($attr{'http-equiv'}) && defined($attr{content})) {
		    $attr{content}= &new_header_value(@attr{'http-equiv', 'content'}) ;
		    delete($attr{'http-equiv'}) unless defined($attr{content}) ;
		    $rebuild= 1 ;
		}
	    } elsif ($tag_name eq 'param') {
		if (lc($attr{valuetype}) eq 'ref') {
		    # Verify that $attr{type} is a valid MIME type.
		    ($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
			if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;

		    my($type)= lc($attr{type}) ;

		    # Remove tag if it links to a script type and scripts are banned.
		    next if $scripts_are_banned_here && $type=~ /^$SCRIPT_TYPE_REGEX$/io ;

		    # Convert value attribute if needed.
		    if (defined($attr{value}) && ($attr{value}=~ /^[\w.+-]+:/)) {

			# Use a local copy of $url_start to call full_url() normally.
			# Only generate new $url_start if the type flag has changed.
			local($url_start)= $url_start ;
			if ($type ne '') {
			    $url_start= $script_url . '/' .
				&pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
					    $e_hide_referer, $e_insert_entry_form,
					    $is_in_frame, $type)
				. '/' ;
			}
			$attr{value}= &full_url($attr{value}) ;
			$rebuild= 1 ;
		    }
		}


	    } elsif ($tag_name eq 'applet') {
		my($codebase_url)= $attr{codebase} ;
		if ($codebase_url ne '') {
		    $codebase_url= 
			  $codebase_url=~ m#^[\w+.-]*:#i ? $codebase_url
			: $codebase_url=~ m#^//#         ? $base_scheme . $codebase_url
			: $codebase_url=~ m#^/#          ? $base_host . $codebase_url
			: $codebase_url=~ m#^\?#         ? $base_file . $codebase_url
			:                                  $base_path . $codebase_url ;
		}
		$attr{codebase}= &full_url($attr{codebase}), $rebuild=1  if defined($attr{codebase}) ;
		local($base_url, $base_scheme, $base_host, $base_path, $base_file)=
		    ($base_url, $base_scheme, $base_host, $base_path, $base_file) ;
		$base_url= $codebase_url, &fix_base_vars  if $codebase_url ne '' ;
		$attr{code}=   &full_url($attr{code}),   $rebuild=1  if defined($attr{code}) ;
		$attr{object}= &full_url($attr{object}), $rebuild=1  if defined($attr{object}) ;

		# archive is a comma-separated list of URIs: split, convert, join.
		$attr{archive}= join(',', map {&full_url($_)} split(/\s*,\s*/, $attr{archive})),
		    $rebuild=1  if defined($attr{archive}) ;
	    } elsif ($tag_name eq 'object') {
		# Verify that $attr{type} is a valid MIME type.
		($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;

		# Verify that $attr{codetype} is a valid MIME type.
		($attr{codetype})= $attr{codetype}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    if  defined($attr{codetype}) && $attr{codetype}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;

		my($type)=     lc($attr{type}) ;
		my($codetype)= lc($attr{codetype}) ;
		my($codebase_url)= $attr{codebase} ;
		next if $scripts_are_banned_here &&
		    (   $type=~     /^$SCRIPT_TYPE_REGEX$/io
		     || $codetype=~ /^$SCRIPT_TYPE_REGEX$/io ) ;
		if ($codebase_url ne '') {
		    $codebase_url= 
			  $codebase_url=~ m#^[\w+.-]*:#i ? $codebase_url
			: $codebase_url=~ m#^//#         ? $base_scheme . $codebase_url
			: $codebase_url=~ m#^/#          ? $base_host . $codebase_url
			: $codebase_url=~ m#^\?#          ? $base_file . $codebase_url
			:                                  $base_path . $codebase_url ;
		}
		$attr{usemap}= &full_url($attr{usemap}), $rebuild=1  if defined($attr{usemap}) ;
		$attr{codebase}= &full_url_by_frame($attr{codebase},1), $rebuild=1
		    if defined($attr{codebase}) ;
		local($base_url, $base_scheme, $base_host, $base_path, $base_file)=
		    ($base_url, $base_scheme, $base_host, $base_path, $base_file) ;
		$base_url= $codebase_url, &fix_base_vars  if $codebase_url ne '' ;
		$attr{archive}= join(' ', map {&full_url_by_frame($_,1)} split(" ", $attr{archive})),
		    $rebuild=1  if defined($attr{archive}) ;
		if (defined($attr{data})) {
		    local($url_start)= $script_url . '/' .
			&pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
				    $e_hide_referer, $e_insert_entry_form, 1, $type)
			. '/' ;
		    $attr{data}= &full_url($attr{data}) ;
		    $rebuild= 1 ;
		}
		if (defined($attr{classid}) && ($attr{classid}!~ /^clsid:/i)) {
		    local($url_start)= $script_url . '/' .
			&pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
				    $e_hide_referer, $e_insert_entry_form, 1,
				    ($codetype ne '')   ? $codetype   : $type )
			. '/' ;
		    $attr{classid}= &full_url($attr{classid}) ;
		    $rebuild= 1 ;
		}
	    } elsif ($tag_name eq 'script') {
		next if $scripts_are_banned_here ;
		($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;
		if (defined($attr{src})) {
		    my($type, $language) ;

		    $type= lc($attr{type}) ;

		    # If there's no type, but there's a language attribute, then
		    #   use that instead to guess the expected type.
		    if (!$type && ($language= $attr{language})) {
			$type= $language=~ /javascript|ecmascript|livescript|jscript/i
							 ? 'application/x-javascript'
			     : $language=~ /css/i        ? 'text/css'
			     : $language=~ /vbscript/i   ? 'application/x-vbscript'
			     : $language=~ /perl/i       ? 'application/x-perlscript'
			     : $language=~ /tcl/i        ? 'text/tcl'
			     :                             ''
		    }

		    # Use a local copy of $url_start to call full_url() normally.
		    # Only generate new $url_start if the type flag has changed.
		    local($url_start)= $url_start ;
		    if ($type) {
			$url_start= $script_url . '/' .
			    &pack_flags($e_remove_cookies, $e_remove_scripts,
					$e_filter_ads, $e_hide_referer,
					$e_insert_entry_form, $is_in_frame, $type)
			    . '/' ;
		    }
		    $attr{src}= &full_url($attr{src}) ;
		    $rebuild= 1 ;

		    # Set $needs_jslib if we're loading an external JavaScript resource.
		    # If $type is empty, then check $default_script_type instead.
		    $needs_jslib= 1
			if ($type || $default_script_type)=~
			    m#^(?:application/x-javascript|application/x-ecmascript|application/javascript|application/ecmascript|text/javascript|text/ecmascript|text/livescript|text/jscript)$#i ;
		}

	    } elsif ($tag_name eq 'style') {
		# Verify that $attr{type} is a valid MIME type.
		($attr{type})= $attr{type}=~ m#^\s*([\w.+\$-]*/[\w.+\$-]*)#, $rebuild=1
		    if  defined($attr{type}) && $attr{type}!~ m#^[\w.+\$-]+/[\w.+\$-]+$# ;

	    } elsif ($tag_name eq 'select') {     # HTML 3.0
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'hr') {         # HTML 3.0
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'td') {         # Netscape extension?
		$attr{background}= &full_url($attr{background}), $rebuild=1 if defined($attr{background}) ;

	    } elsif ($tag_name eq 'th') {         # Netscape extension?
		$attr{background}= &full_url($attr{background}), $rebuild=1 if defined($attr{background}) ;

	    } elsif ($tag_name eq 'tr') {         # Netscape extension?
		$attr{background}= &full_url($attr{background}), $rebuild=1 if defined($attr{background}) ;

	    } elsif ($tag_name eq 'table') {      # Netscape extension?
		$attr{background}= &full_url($attr{background}), $rebuild=1 if defined($attr{background}) ;

	    } elsif ($tag_name eq 'bgsound') {    # Microsoft only
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'blockquote') {
		$attr{cite}= &full_url($attr{cite}), $rebuild=1  if defined($attr{cite}) ;

	    } elsif ($tag_name eq 'del') {
		$attr{cite}= &full_url($attr{cite}), $rebuild=1  if defined($attr{cite}) ;

	    } elsif ($tag_name eq 'embed') {      # Netscape only
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;
		$attr{pluginspage}= &full_url($attr{pluginspage}),  $rebuild=1  if defined($attr{pluginspage}) ;

	    } elsif ($tag_name eq 'fig') {        # HTML 3.0
		$attr{src}=      &full_url($attr{src}),      $rebuild=1  if defined($attr{src}) ;
		$attr{imagemap}= &full_url($attr{imagemap}), $rebuild=1  if defined($attr{imagemap}) ;

	    } elsif ($tag_name=~ /^h[1-6]$/) {    # HTML 3.0
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'ilayer') {
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'ins') {
		$attr{cite}= &full_url($attr{cite}), $rebuild=1  if defined($attr{cite}) ;

	    } elsif ($tag_name eq 'note') {       # HTML 3.0
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;

	    } elsif ($tag_name eq 'overlay') {    # HTML 3.0
		$attr{src}=      &full_url($attr{src}),      $rebuild=1  if defined($attr{src}) ;
		$attr{imagemap}= &full_url($attr{imagemap}), $rebuild=1  if defined($attr{imagemap}) ;

	    } elsif ($tag_name eq 'q') {
		$attr{cite}= &full_url($attr{cite}), $rebuild=1  if defined($attr{cite}) ;

	    } elsif ($tag_name eq 'ul') {         # HTML 3.0
		$attr{src}=  &full_url($attr{src}),  $rebuild=1  if defined($attr{src}) ;



	    }   #####   END OF TAG-SPECIFIC PROCESSING   #####

	    if ($rebuild) {
		my($name, $value, $attrs, $end_slash) ;

		while (($name, $value)= each %attr) {
		    next unless defined($value) ;
		    $attrs.= (' ' . $name), next   if $value eq '' ;

		    $value=~ s/&/&amp;/g ;
		    $value=~ s/</&lt;/g ;
		    $value=~ s/>/&gt;/g ;
		    if ($value!~ /"/ || $value=~ /'/) {
			$value=~ s/"/&quot;/g ;  # only needed when using double quotes
			$attrs.= join('', ' ', $name, '="', $value, '"') ;
		    } else {
			$attrs.= join('', ' ', $name, "='", $value, "'") ;
		    }
		}

		$end_slash= $tag=~ m#/\s*>?$#   ? ' /'   : '' ;
		$tag= "<$tag_name$attrs$end_slash>" ;
	    }

	    push(@out, $tag) ;
	} elsif ($comment) {
	    if ( $comment=~ /^<!--\s*&{/ ) {

		# Remove the whole conditional comment if scripts are banned.
		next if $scripts_are_banned_here ;  # remove it by not doing push(@out)

		
		my($condition, $contents, $end)=
		    $comment=~ /^<!--\s*&{(.*?)}\s*;(.*?)(--\s*)?>$/s ;
		$condition= &proxify_block($condition, $default_script_type)
		    if $PROXIFY_SCRIPTS ;
		$contents=  &proxify_html(\$contents, 0, $no_exit_on_frameset) ;
		$comment= join('', '<!--&{', $condition, '};', $contents, $end, '>') ;
	    } elsif ( $comment=~ /^<!--\s*\[\s*if/i ) {

		# Proxify the contents of the comment.
		my($start, $contents, $end)=
		    $comment=~ /^(<!--[^>]*?>)(.*?)(<!\s*\[\s*endif[^>]*?>)$/is ;
		$contents=  &proxify_html(\$contents, 0, 1) ;
		$comment= "$start$contents$end" ;
	    } elsif ($PROXIFY_COMMENTS) {
		my($contents, $end)= $comment=~ /^<!--(.*?)(--\s*)?>$/s ;
		$contents=  &proxify_html(\$contents, 0, 1) ;
		$comment= "<!--$contents$end>" ;
	    }

	    push(@out, $comment) ;
	} elsif ($script_block) {
	    my($tag, $script, $attrs, %attr, $type, $language, $name) ;

	    # Parse the <script> block.
	    ($tag, $script)=
		$script_block=~ m#^(<\s*script\b[^>]*>)(.*)<\s*/script\b.*?>\z#si ;
	    $tag= &proxify_html(\$tag, 0) ;

	    ($attrs)= $tag=~ /^<\s*script\b([^>]*)>/ ;
	    while ($attrs=~ /([A-Za-z][\w.:-]*)\s*(?:=\s*(?:"([^">]*)"?|'([^'>]*)'?|([^'"][^\s>]*)))?/g ) {
		$name= lc($1) ;
		next if exists($attr{$name}) ;   # duplicate attr
		$attr{$name}= &HTMLunescape(defined($2) ? $2
					  : defined($3) ? $3
					  : defined($4) ? $4
					  : '' ) ;
	    }
	    $type= lc($attr{type}) ;
	    if (!$type && ($language= $attr{language})) {
		$type= $language=~ /javascript|ecmascript|livescript|jscript/i
						 ? 'application/x-javascript'
		     : $language=~ /css/i        ? 'text/css'
		     : $language=~ /vbscript/i   ? 'application/x-vbscript'
		     : $language=~ /perl/i       ? 'application/x-perlscript'
		     : $language=~ /tcl/i        ? 'text/tcl'
		     :                             ''
	    }
	    $type||= $default_script_type ;
	    if ($type=~ m#^(application/x-javascript|application/x-ecmascript|application/javascript|application/ecmascript|text/javascript|text/ecmascript|text/livescript|text/jscript)$#i) {
		while (&js_script_ends_inside_string(\$script)) {
		    $$body_ref=~ m#\G(.*?)<\s*/script\b.*?>#sgci ;
		    $script.= "<\\/script>" . $1 ;
		}
	    }


	    # If needed, remove script altogether by not doing push(@out).
	    next if $scripts_are_banned_here ;

	    # Proxify the script content if needed.
	    $script= &proxify_block($script, $type) if $PROXIFY_SCRIPTS ;

	    push(@out, $tag, $script, '</script>') ;
	} elsif ($style_block) {
	    my($tag, $attrs, $stylesheet, $type) ;

	    ($tag, $stylesheet)=
		$style_block=~ m#^(<\s*style\b[^>]*>)(.*?)<\s*/style\b.*?>#si ;
	    $tag= &proxify_html(\$tag, 0) ;
	    ($attrs)= $tag=~ /^<\s*style\b([^>]*)>/ ;
	    while ($attrs=~ /([A-Za-z][\w.:-]*)\s*(?:=\s*(?:"([^">]*)"?|'([^'>]*)'?|([^'"][^\s>]*)))?/g ) {
		$type= lc(&HTMLunescape(defined($2) ? $2
				      : defined($3) ? $3
				      : defined($4) ? $4
				      : '' )), last
		    if lc($1) eq 'type' ;
	    }

	    $type||= $default_style_type ;
	    next if $scripts_are_banned_here && $type=~ /^$SCRIPT_TYPE_REGEX$/io ;
	    $stylesheet= &proxify_block($stylesheet, $type) ;

	    push(@out, $tag, $stylesheet, '</style>') ;
	} elsif ($decl_bang) {
	    my($inside, @words, $q, $rebuild) ;
	    ($inside)= $decl_bang=~ /^<!([^>]*)/ ;
	    @words= $inside=~ /\s*("[^">]*"?|'[^'>]*'?|[^'"][^\s>]*)/g ;
	    foreach (@words) {
		if (m#^["']?[\w+.-]+://#) {
		    if    (/^"/)  { $q= '"' ; s/^"|"$//g }
		    elsif (/^'/)  { $q= "'" ; s/^'|'$//g }
		    else          { $q= '' }

		    $_= $q . &HTMLescape(&full_url(&HTMLunescape($_))) . $q ;
		    $rebuild= 1 ;
		}
	    }
	    $decl_bang= '<!' . join(' ', @words) . '>'   if $rebuild ;

	    push(@out, $decl_bang) ;
	# Handle any <?...?> declarations, such as XML declarations.
	} elsif ($decl_question) {

	    # Nothing needs to be done to these.
	    push(@out, $decl_question) ;
	}  # end of main if comment/script/style/declaration/tag block
    } continue {
	$first_script_pos= $out_start
	    if $needs_jslib && !defined($first_script_pos) ;

    }  # end of main while loop
    #   @out now has proxified HTML
    if ($is_full_page) {
	my($after_decl, $i) ;
	for ($i= 0; $i<@out; $i++) {
	    next unless $out[$i]=~ /^</ ;
	    $after_decl= $i+1, next if $out[$i]=~ /^<\s*(?:\?|!)/ ;
	    last ;   # if it's any other tag
	}
	splice(@out, ($body_pos || $html_pos || $after_decl), 0, $full_insertion)
	    if $doing_insert_here && $has_content ;

	if ($PROXIFY_SCRIPTS && $needs_jslib) {
	    my($base_url_jsq, $default_script_type_jsq, $default_style_type_jsq,
	       $p_cookies_are_banned_here, $p_doing_insert_here, $p_session_cookies_only,
	       $p_cookie_path_follows_spec, $p_respect_three_dot_rule, $insert_pos) ;
	    my($jslib_block)= '<script type="text/javascript" src="'
			    . &HTMLescape($url_start . &wrap_proxy_encode('x-proxy://scripts/jslib'))
			    . "\"></script>\n" ;
	    # Create JS double-quoted string of base URL and other vars.
	    ($base_url_jsq=            $base_url           )=~ s/(["\\])/\\$1/g ;
	    ($default_script_type_jsq= $default_script_type)=~ s/(["\\])/\\$1/g ;
	    ($default_style_type_jsq=  $default_style_type )=~ s/(["\\])/\\$1/g ;
	    $p_cookies_are_banned_here=  $cookies_are_banned_here   ? 'true'  : 'false' ;
	    $p_doing_insert_here=        $doing_insert_here         ? 'true'  : 'false' ;
	    $p_session_cookies_only=     $SESSION_COOKIES_ONLY      ? 'true'  : 'false' ;
	    $p_cookie_path_follows_spec= $COOKIE_PATH_FOLLOWS_SPEC  ? 'true'  : 'false' ;
	    $p_respect_three_dot_rule=   $RESPECT_THREE_DOT_RULE    ? 'true'  : 'false' ;
	    $insert_pos= $head_pos || $html_pos || $after_decl ;
	    $insert_pos= $first_script_pos
		if defined($first_script_pos) && $first_script_pos<$insert_pos ;
	    splice(@out, $insert_pos, 0, $jslib_block,
		   qq(<script type="text/javascript">_proxy_jslib_pass_vars("$base_url_jsq",$p_cookies_are_banned_here,$p_doing_insert_here,$p_session_cookies_only,$p_cookie_path_follows_spec,$p_respect_three_dot_rule,"$default_script_type_jsq","$default_style_type_jsq");</script>\n)
		  ) ;
	}

	# Prepend newline if it's after any declarations.
	splice(@out, $after_decl, 0, ($after_decl ? "\n" : ''),
	       "<!-- resource has been modified by proxy -->\n") ;
    }
    return join('', @out) ;

}  # sub proxify_html()

sub full_url {
    my($uri_ref)= @_ ;

    $uri_ref=~ s/^\s+|\s+$//g ;  # remove leading/trailing whitespace
    return undef if $uri_ref=~ m#^x-proxy:#i ;
    if ($uri_ref=~ /^(?:javascript|livescript):/i) {
	return undef if $scripts_are_banned_here ;
	return $uri_ref unless $PROXIFY_SCRIPTS ;
	my($script)= $uri_ref=~ /^(?:javascript|livescript):(.*)$/si ;
	return 'javascript:' . &proxify_block($script, 'application/x-javascript') ;
    }

    # Separate fragment from URI
    my($uri,$frag)= $uri_ref=~ /^([^#]*)(#.*)?/ ;
    return $uri_ref if $uri eq '' ;  # allow bare fragments to pass unchanged
    $uri=~ s/[\015\012]//g ;
    my($absurl)=
	    $uri=~ m#^[\w+.-]*:#i   ?  $uri                 # absolute URL
	  : $uri=~ m#^//#           ?  $base_scheme . $uri  # net_path (rare)
	  : $uri=~ m#^/#            ?  $base_host . $uri    # abs_path, rel URL
	  : $uri=~ m#^\?#           ?  $base_file . $uri    # abs_path, rel URL
	  :                            $base_path . $uri ;  # relative path

    return $url_start . &wrap_proxy_encode($absurl) . $frag ;
}
sub full_url_by_frame {
    my($uri_ref, $is_frame)= @_ ;
    local($url_start)= $is_frame   ? $url_start_inframe  : $url_start_noframe ;
    return &full_url($uri_ref) ;
}
sub fix_base_vars {
    $base_url=~ s/\A\s+|\s+\Z//g ;  # remove leading/trailing spaces

    # Guarantee that $base_url has at least a path of '/', inserting before
    #   ?query if needed.
    $base_url=~ s#^([\w+.-]+://[^/?]+)/?#$1/# ;

    ($base_scheme)= $base_url=~ m#^([\w+.-]+:)//# ;
    ($base_host)=   $base_url=~ m#^([\w+.-]+://[^/?]+)# ; # no ending slash
    ($base_path)=   $base_url=~ m#^([^?]*/)# ;            # use greedy matching
    ($base_file)=   $base_url=~ m#^([^?]*)# ;
}

sub wrap_proxy_encode {
    my($URL)= @_ ;

    my($uri,$frag)= $URL=~ /^([^#]*)(#.*)?/ ;

    $uri= &proxy_encode($uri) ;

    # Encode ? so it doesn't prematurely end PATH_INFO.
    $uri=~ s/=/=3d/g ; $uri=~ s/\?/=3f/g ; $uri=~ s/%/=25/g ;
    1 while $uri=~ s#//#/=2f#g ;    # work around Apache PATH_INFO bug

    return $uri . $frag ;
}


sub wrap_proxy_decode {
    my($enc_URL)= @_ ;

    my($uri,$frag)= $enc_URL=~ /^([^#]*)(#.*)?/ ;

    # First, un-encode =xx chars.
    $uri=~ s/=(..)/chr(hex($1))/ge ;

    $uri= &proxy_decode($uri) ;

    return $uri . $frag ;
}
sub proxify_block {
    my($s, $type)= @_ ;

    if ($scripts_are_banned_here) {
	return undef if $type=~ /^$SCRIPT_TYPE_REGEX$/io ;
    }

    if ($type eq 'text/css') {

	$s=~ s/url\s*\(\s*(([^)]*\\\))*[^)]*)(\)|$)/
	      'url(' . &css_full_url($1) . ')'     /gie ;

	$s=~ s#\@import\s*("[^"]*"|'[^']*'|(?!url\s*\()[^;\s<]*)#
	       '@import ' . &css_full_url($1)                   #gie ;

	return $s ;
    } elsif ($type=~ m#^(application/x-javascript|application/x-ecmascript|application/javascript|application/ecmascript|text/javascript|text/ecmascript|text/livescript|text/jscript)$#i) {

	return $s unless $PROXIFY_SCRIPTS ;

	return &proxify_js($s, 1) ;


    # For any non-supported script type, either remove it or pass it unchanged.
    } elsif ($type=~ /^$SCRIPT_TYPE_REGEX$/io) {
	return $ALLOW_UNPROXIFIED_SCRIPTS  ? $s  : '' ;


    } else {
	
	return $s ;

    }

}

sub css_full_url {
    my($url)= @_ ;
    my($q) ;

    $url=~ s/\s+$// ;       # leading spaces already stripped above
    if    ($url=~ /^"/)  { $q= '"' ; $url=~ s/^"|"$//g }  # strip quotes
    elsif ($url=~ /^'/)  { $q= "'" ; $url=~ s/^'|'$//g }
    $url=~ s/\\(.)/$1/g ;   # "\"-unescape
    $url=~ s/^\s+|\s+$//g ; # finally, strip spaces once more

    $url= &full_url($url) ;

    $url=~ s/([(),\s'"\\])/\\$1/g ;    # put "\"-escaping back in

    return $q . $url . $q ;
}

sub http_get {
    my($default_port, $portst, $realhost, $realport, $request_uri,
       $realm, $tried_realm, $auth,
       $proxy_auth_header, $content_type,
       $lefttoget, $postblock, @postbody, $body_too_big, $rin,
       $status_code, $footers) ;
    local($/)= "\012" ;
    local(*S, *S_PLAIN) ;
    if ($scheme eq 'https') {
	eval { require Net::SSLeay } ;  # don't check during compilation
	&no_SSL_warning($URL) if $@ ;
	&insecure_die  if !$RUNNING_ON_SSL_SERVER && !$OVERRIDE_SECURITY ;
    }


    $default_port= $scheme eq 'https'  ? 443  : 80 ;

    $port= $default_port if $port eq '' ;

    # Some servers don't like default port in a Host: header, so use $portst.
    $portst= ($port==$default_port)  ? ''  : ":$port" ;

    $realhost= $host ;
    $realport= $port ;
    $request_uri= $path ;

    # there must be a smoother way to handle proxies....
    if ($scheme eq 'http' && $HTTP_PROXY) {
	my($dont_proxy) ;
	foreach (@NO_PROXY) {
	    $dont_proxy= 1, last if $host=~ /$_$/i ;
	}
	unless ($dont_proxy) {
	    ($realhost, $realport)=
		$HTTP_PROXY=~ m#^(?:http://)?([^/?:]*):?([^/?]*)#i ;
	    $realport= 80 if $realport eq '' ;
	    $request_uri= $URL ;
	    $proxy_auth_header= "Proxy-Authorization: Basic $PROXY_AUTH\015\012"
	       if $PROXY_AUTH ne '' ;
	}
    }
    HTTP_GET: {
	if ($scheme eq 'https') {
	    my($dont_proxy) ;
	    if ($SSL_PROXY) {
		foreach (@NO_PROXY) {
		    $dont_proxy= 1, last if $host=~ /$_$/i ;
		}
	    }
	    if ($SSL_PROXY && !$dont_proxy) {
		($realhost, $realport)=
		    $SSL_PROXY=~ m#^(?:http://)?([^/?:]*):?([^/?]*)#i ;
		$realport= 80 if $realport eq '' ;
		&newsocketto('S_PLAIN', $realhost, $realport) ;

		# Send CONNECT request.
		print S_PLAIN "CONNECT $host:$port HTTP/$HTTP_VERSION\015\012",
			      'Host: ', $host, $portst, "\015\012" ;
		print S_PLAIN "Proxy-Authorization: Basic $SSL_PROXY_AUTH\015\012"
		    if $SSL_PROXY_AUTH ne '' ;
		print S_PLAIN "\015\012" ;

		# Wait a minute for the response to start
		vec($rin= '', fileno(S_PLAIN), 1)= 1 ;
		select($rin, undef, undef, 60)
		    || &HTMLdie("No response from SSL proxy") ;
		my($response, $status_code) ;
		do {
		    $response= '' ;
		    do {
			$response.= $_= <S_PLAIN> ;
		    } until (/^(\015\012|\012)$/) ; #lines end w/ LF or CRLF
		    ($status_code)= $response=~ m#^HTTP/\d+\.\d+\s+(\d+)# ;
		} until $status_code ne '100' ;

		# Any 200-level response is OK; fail otherwise.
		&HTMLdie("SSL proxy error; response was:<p><pre>$response</pre>")
		    unless $status_code=~ /^2/ ;

	    # If not using a proxy, then open a socket directly to the server.
	    } else {
		&newsocketto('S_PLAIN', $realhost, $realport) ;
	    }

	    # Either way, make an SSL socket S tied to the plain socket S_PLAIN.
	    tie(*S, 'SSL_Handle', \*S_PLAIN) ;


	# If not using SSL, then just open a normal socket.  Any proxy is
	#   already set in $realhost and $realport, above.
	} else {
	    &newsocketto('S', $realhost, $realport) ;
	}


	binmode S ;   # see note with "binmode STDOUT", above
	print S $ENV{'REQUEST_METHOD'}, ' ', $request_uri, " HTTP/$HTTP_VERSION\015\012",
		'Host: ', $host, $portst, "\015\012",    # needed for multi-homed servers
		'Accept: ', $env_accept, "\015\012",     # possibly modified
		'User-Agent: ', $USER_AGENT || $ENV{'HTTP_USER_AGENT'}, "\015\012",
		"Accept-Encoding: ,\015\012",
		"Accept-Language: $ENV{HTTP_ACCEPT_LANGUAGE}\015\012",
		$proxy_auth_header ;                     # empty if not needed
	if (!$e_hide_referer) {
	    my($referer)= $ENV{'HTTP_REFERER'} ;
	    if (@PROXY_GROUP) {
		foreach (@PROXY_GROUP) {
		    print(S 'Referer: ', &wrap_proxy_decode($referer), "\015\012"), last
			if  $referer=~ s#^$_(/[^/]*/?)?##  &&  ($referer ne '') ;
		    last if $referer eq '' ;
		}
	    } else {
		print S 'Referer: ', &wrap_proxy_decode($referer), "\015\012"
		    if  $referer=~ s#^$THIS_SCRIPT_URL(/[^/]*/?)?##  &&  ($referer ne '') ;
	    }
	}


	# Add "Connection: close" header if we're using HTTP 1.1.
	print S "Connection: close\015\012"  if $HTTP_VERSION eq '1.1' ;

	# Add the cookie if it exists and cookies aren't banned here.
	print S 'Cookie: ', $cookie_to_server, "\015\012"
	    if !$cookies_are_banned_here && ($cookie_to_server ne '') ;

	print S "Pragma: $ENV{HTTP_PRAGMA}\015\012" if $ENV{HTTP_PRAGMA} ne '' ;
	print S "Cache-Control: $ENV{HTTP_CACHE_CONTROL}\015\012"
	    if $ENV{HTTP_CACHE_CONTROL} ne '' ;


	# Add Authorization: header if we've had a challenge.
	if ($realm ne '') {
	    # If we get here, we know $realm has a defined $auth and has not
	    #   been tried.
	    print S 'Authorization: Basic ', $auth{$realm}, "\015\012" ;
	    $tried_realm= $realm ;

	} else {
	   
	    if ($username ne '') {
		print S 'Authorization: Basic ',
			&base64($username . ':' . $password),
			"\015\012" ;
	    } elsif ( ($tried_realm,$auth)= each %auth ) {
		print S 'Authorization: Basic ', $auth, "\015\012" ;
	    }
	}
	if ($ENV{'REQUEST_METHOD'} eq 'POST') {

	    if ($body_too_big) {
		# Quick 'n' dirty response for an unlikely occurrence.
		# 413 is not actually an HTTP/1.0 response...
		&HTMLdie("Sorry, this proxy can't handle a request larger "
		       . "than $MAX_REQUEST_SIZE bytes at a password-protected"
		       . " URL.  Try reducing your submission size, or submit "
		       . "it to an unprotected URL.", 'Submission too large',
			 '413 Request Entity Too Large') ;
	    }

	    # Otherwise...
	    $lefttoget= $ENV{'CONTENT_LENGTH'} ;
	    print S 'Content-type: ', $ENV{'CONTENT_TYPE'}, "\015\012",
		    'Content-length: ', $lefttoget, "\015\012\015\012" ;

	    if (@postbody) {
		print S @postbody ;
	    } else {
		$body_too_big= ($lefttoget > $MAX_REQUEST_SIZE) ;

		# Loop to guarantee all is read from STDIN.
		do {
		    $lefttoget-= read(STDIN, $postblock, $lefttoget) ;
		    print S $postblock ;
		    # efficient-- only doing test when input is slow anyway.
		    push(@postbody, $postblock) unless $body_too_big ;
		} while $lefttoget && ($postblock ne '') ;
	    }

	# For GET or HEAD requests, just add extra blank line.
	} else {
	    print S "\015\012" ;
	}


	# Wait a minute for the response to start
	vec($rin= '', fileno(S), 1)= 1 ;
	select($rin, undef, undef, 60)
	    || &HTMLdie("No response from $realhost:$realport") ;

	$status= <S> ;  # first line, which is the status line in HTTP 1.x


	unless ($status=~ m#^HTTP/#) {
	    $is_html= 1 ;   # HTTP 0.9 by definition implies an HTML response
	    $content_type= 'text/html' ;
	    undef $/ ;
	    $body= $status . <S> ;
	    $status= '' ;

	    close(S) ;
	    untie(*S) if $scheme eq 'https' ;
	    return ;
	}
	do {
	    ($status_code)= $status=~ m#^HTTP/\d+\.\d+\s+(\d+)# ;

	    $headers= '' ;   # could have been set by first attempt
	    do {
		$headers.= $_= <S> ;    # $headers includes last blank line
#	    } until (/^(\015\012|\012)$/) || eof(S) ; # lines end w/ LF or CRLF
	    } until (/^(\015\012|\012)$/) || $_ eq '' ; #lines end w/ LF or CRLF

	    $status= <S> if $status_code == 100 ;  # re-read for next iteration
	} until $status_code != 100 ;

	# Unfold long header lines, a la RFC 822 section 3.1.1
	$headers=~ s/(\015\012|\012)[ \t]+/ /g ;


	# Check for 401 Unauthorized response
	if ($status=~ m#^HTTP/\d+\.\d+\s+401\b#) {
	    ($realm)=
		$headers=~ /^WWW-Authenticate:\s*Basic\s+realm="([^"\n]*)/mi ;
	    &HTMLdie("Error by target server: no WWW-Authenticate header.")
		unless $realm ne '' ;

	    if ($auth{$realm} eq '') {
		&get_auth_from_user($host, $realm, $URL) ;
	    } elsif ($realm eq $tried_realm) {
		&get_auth_from_user($host, $realm, $URL, 1) ;
	    }

	    # so now $realm exists, has defined $auth, and has not been tried
	    close(S) ;
	    untie(*S) if $scheme eq 'https' ;
	    redo HTTP_GET ;
	}


	# Extract $content_type, used in several places
	($content_type)= $headers=~ m#^Content-Type:\s*([\w/.+\$-]*)#mi ;
	$content_type= lc($content_type) ;

	if ($TEXT_ONLY) {
	    if ( ($content_type ne '') && ($content_type!~ m#^text/#i) ) {
		&non_text_die ;
	    }
	}

	# If we're removing scripts, then disallow script MIME types.
	if ($scripts_are_banned_here) {
	    &script_content_die  if $content_type=~ /^$SCRIPT_TYPE_REGEX$/io ;
	}


	if ($images_are_banned_here) {
	    &skip_image  unless $content_type=~ m#^text/#i ;
	}
	if        ($headers=~ m#^Content-Base:\s*([\w+.-]+://\S+)#mi) {
	    $base_url= $1, &fix_base_vars ;
	} elsif   ($headers=~ m#^Content-Location:\s*([\w+.-]+://\S+)#mi) {
	    $base_url= $1, &fix_base_vars ;
	} elsif   ($headers=~ m#^Location:\s*([\w+.-]+://\S+)#mi) {
	    $base_url= $1, &fix_base_vars ;
	}
	&http_fix ;
	if ($MINIMIZE_CACHING) {
	    my($new_value)= 'no-cache' ;
	    $new_value.= ', no-store'
		if $headers=~ /^Cache-Control:.*?\bno-store\b/mi ;
	    $new_value.= ', no-transform'
	      if $headers=~ /^Cache-Control:.*?\bno-transform\b/mi ;

	    my($no_cache_headers)=
		"Cache-Control: $new_value\015\012Pragma: no-cache\015\012" ;

	    $headers=~ s/^Cache-Control:[^\012]*\012?//mig ;
	    $headers=~ s/^Pragma:[^\012]*\012?//mig ;
	    $headers=~ s/^Expires:[^\012]*\012?//mig ;

	    $headers= $no_cache_headers . $headers ;
	}

	$is_html= 1  if $content_type eq 'text/html' ;
	$is_html= 1  if ($content_type eq '') ;
	$status=~ s#^\S+#Status:#  if $NOT_RUNNING_AS_NPH ;
	if ($ENV{'REQUEST_METHOD'} ne 'HEAD') {
	    if (    ($expected_type=~ /^$TYPES_TO_HANDLE_REGEX$/io)
		 || ($content_type=~  /^$TYPES_TO_HANDLE_REGEX$/io)   ) {

		my($type) ;
		if ( ($expected_type eq 'text/css') || ($content_type eq 'text/css') ) {
		    $type= 'text/css' ;
		} elsif ($expected_type=~ /^$TYPES_TO_HANDLE_REGEX$/io) {
		    $type= $expected_type ;
		} else {
		    $type= $content_type ;
		}
		if ($headers=~ /^Transfer-Encoding:[ \t]*chunked\b/mi) {
		    ($body, $footers)= &get_chunked_body('S') ;
		    &HTMLdie(&HTMLescape("Error reading chunked response from $URL ."))
			unless defined($body) ;
		    $headers=~ s/^Transfer-Encoding:[^\012]*\012?//mig ;
		    $headers=~ s/^(\015\012|\012)/$footers$1/m ;

		# If not chunked, read entire input into $body.
		} else {
		    undef $/ ;
		    $body= <S> ;
		}
		if (($content_type eq 'text/html') and $body=~ /^\s*<(?:\!(?!--\s*\n)|html)/) {
		    $type= 'text/html' ;
		    $is_html= 1 ;

		} else {

		    $body= &proxify_block($body, $type) ;
		    $headers=~ s/^Content-Length:.*/
				 'Content-Length: ' . length($body) /mie ;

		    print $status, $headers, $body ;
		    $response_sent= 1 ;
		}


	    } elsif ($is_html) {
		# If response is chunked, handle as above; see comments there.
		if ($headers=~ /^Transfer-Encoding:[ \t]*chunked\b/mi) {
		    ($body, $footers)= &get_chunked_body('S') ;
		    &HTMLdie(&HTMLescape("Error reading chunked response from $URL ."))
			unless defined($body) ;
		    $headers=~ s/^Transfer-Encoding:[^\012]*\012?//mig ;
		    $headers=~ s/^(\015\012|\012)/$footers$1/m ;

		# Handle explicitly sized response.
		} elsif ($headers=~ /^Content-Length:[ \t]*(\d+)/mi) {
		    $body= &read_socket('S', $1) ;

		# If not chunked or sized, read entire input into $body.
		} else {
		    undef $/ ;
		    $body= <S> ;
		}
	    } else {
		my($buf) ;
		print $status, $headers ;

		# If using SSL, read() could return 0 and truncate data. :P
		print $buf while read(S, $buf, 16384) ;

		$response_sent= 1 ;

	    }

	} else {
	    $body= '' ;
	}

	close(S) ;
	untie(*S) if $scheme eq 'https' ;

    }  # HTTP_GET:

}  # sub http_get()


BEGIN {
    package SSL_Handle ;

    use vars qw($SSL_CONTEXT  $DEFAULT_READ_SIZE) ;

    $DEFAULT_READ_SIZE= 512 ;

    sub TIEHANDLE {
	my($class, $socket, $unbuffered)= @_ ;
	my($ssl) ;

	# $SSL_CONTEXT only needs to be created once (e.g. with mod_perl).
	unless ($SSL_CONTEXT) {
	    # load_error_strings() is only worth the effort when using mod_perl
	    Net::SSLeay::load_error_strings() if $ENV{'MOD_PERL'} ;
	    Net::SSLeay::SSLeay_add_ssl_algorithms() ;
	    Net::SSLeay::randomize() ;

	    # Create the reusable SSL context
	    $SSL_CONTEXT= Net::SSLeay::CTX_new()
		or &main::HTMLdie("Can't create SSL context: $!") ;

	    # Need this to cope with bugs in some other SSL implementations.
	    Net::SSLeay::CTX_set_options($SSL_CONTEXT, &Net::SSLeay::OP_ALL)
		and &main::HTMLdie("Can't set options on SSL context: $!");
	}

	$ssl = Net::SSLeay::new($SSL_CONTEXT)
	    or &main::HTMLdie("Can't create SSL connection: $!");
	Net::SSLeay::set_fd($ssl, fileno($socket))
	    or &main::HTMLdie("Can't set_fd: $!") ;
	Net::SSLeay::connect($ssl) or &main::HTMLdie("Can't SSL connect: $!") ;

	bless { SSL      => $ssl,
		socket   => $socket,
		readsize => ($unbuffered  ? 0  : $DEFAULT_READ_SIZE),
		buf      => '',
		eof      => '',
	      },
	    $class ;  # returns reference
    }


    # For the print() function.  Respect $, and $\ settings.
    sub PRINT {
	my($self)= shift ;
	my($written, $errs)=
	    Net::SSLeay::ssl_write_all($self->{SSL}, join($, , @_) . $\ ) ;
	&main::HTMLdie("Net::SSLeay::ssl_write_all error: $errs") if $errs ne '' ;
	return 1 ;   # to keep consistent with standard print()
    }

    sub READ {
	my($self)= shift ;
	return 0 if $self->{eof} ;

	# Can't use my(undef) in some old versions of Perl, so use $dummy.
	my($dummy, $len, $offset)= @_ ;   # $_[0] is handled explicitly below
	my($read, $errs) ;

	# this could be cleaned up....
	if ($len > length($self->{buf})) {
	    if ( $offset || ($self->{buf} ne '') ) {
		$len-= length($self->{buf}) ;
		#$read= Net::SSLeay::ssl_read_all($self->{SSL}, $len) ;
		($read, $errs)= &ssl_read_all_fixed($self->{SSL}, $len) ;
		&main::HTMLdie("ssl_read_all_fixed() error: $errs") if $errs ne '' ;
		return undef unless defined($read) ;
		$self->{eof}= 1  if length($read) < $len ;
		my($buflen)= length($_[0]) ;
		$_[0].= "\0" x ($offset-$buflen)  if $offset>$buflen ;
		substr($_[0], $offset)= $self->{buf} . $read ;
		$self->{buf}= '' ;
		return length($_[0])-$offset ;
	    } else {
		# Streamlined block for the most common case.
		#$_[0]= Net::SSLeay::ssl_read_all($self->{SSL}, $len) ;
		($_[0], $errs)= &ssl_read_all_fixed($self->{SSL}, $len) ;
		&main::HTMLdie("ssl_read_all_fixed() error: $errs") if $errs ne '' ;
		return undef unless defined($_[0]) ;
		$self->{eof}= 1  if length($_[0]) < $len ;
		return length($_[0]) ;
	    }
	} else {
	    # Here the ?: operator returns an lvar.
	    ($offset  ? substr($_[0], $offset)  : $_[0])=
		substr($self->{buf}, 0, $len) ;
	    substr($self->{buf}, 0, $len)= '' ;
	    return $len ;
	}
    }
    sub READLINE {
	my($self)= shift ;
	my($read, $errs) ;
	if (defined($/)) {
	    if (wantarray) {
		return () if $self->{eof} ;
		($read, $errs)= &ssl_read_all_fixed($self->{SSL}) ;
		&main::HTMLdie("ssl_read_all_fixed() error: $errs") if $errs ne '' ;
		# Prepend current buffer, and split to end items on $/ or EOS;
		#   this regex prevents final '' element.
		$self->{eof}= 1 ;
		return ($self->{buf} . $read)=~ m#(.*?\Q$/\E|.+?\Z(?!\n))#sg ;
	    } else {
		return '' if $self->{eof} ;
		my($pos, $read, $ret) ;
		while ( ($pos= index($self->{buf}, $/)) == -1 ) {
		  $read= Net::SSLeay::read($self->{SSL}, $self->{readsize} || 1 ) ;
		  return undef if $errs = Net::SSLeay::print_errs('SSL_read') ;
		  $self->{eof}= 1, return $self->{buf}  if $read eq '' ;
		  $self->{buf}.= $read ;
		}
		$pos+= length($/) ;
		$ret= substr($self->{buf}, 0, $pos) ;
		substr($self->{buf}, 0, $pos)= '' ;
		return $ret ;
	    }
	} else {
	    return '' if $self->{eof} ;
	    ($read, $errs)= &ssl_read_all_fixed($self->{SSL}) ;
	    &main::HTMLdie("ssl_read_all_fixed() error: $errs") if $errs ne '' ;
	    $self->{eof}= 1 ;
	    return  $self->{buf} . $read ;
	}
    }


    # Used when closing socket, or from UNTIE() or DESTROY() if needed.
    #   Calling Net::SSLeay::free() twice on the same object causes a crash,
    #   so be careful not to do that.
    sub CLOSE {
	my($self)= shift ;
	my($errs) ;
	$self->{eof}= 1 ;
	$self->{buf}= '' ;
	if (defined($self->{SSL})) {
	    Net::SSLeay::free($self->{SSL}) ;
	    delete($self->{SSL}) ;  # to detect later if we've free'd it or not
	    &main::HTMLdie("Net::SSLeay::free error: $errs")
		if $errs= Net::SSLeay::print_errs('SSL_free') ;
	    close($self->{socket}) ;
	}
    }

    sub UNTIE {
	my($self)= shift ;
	$self->CLOSE ;
    }
    sub DESTROY {
	my($self)= shift ;
	$self->CLOSE ;
    }
    sub FILENO {
	my($self)= shift ;
	return fileno($self->{socket}) ;
    }

    sub EOF {
	my($self)= shift ;
	return 1 if $self->{eof} ;        # overrides anything left in {buf}
	return 0 if $self->{buf} ne '' ;
	return eof($self->{socket}) ;
    }

    sub BINMODE {
	my($self)= shift ;
	binmode($self->{socket}) ;
    }

    sub ssl_read_all_fixed {
	my ($ssl,$how_much) = @_;
	$how_much = 2000000000 unless $how_much;
	my ($got, $errs);
	my $reply = '';

	while ($how_much > 0) {
	    $got = Net::SSLeay::read($ssl,$how_much);
	    last if $errs = Net::SSLeay::print_errs('SSL_read');
	    $how_much -= Net::SSLeay::blength($got);
	    last if $got eq '';  # EOF
	    $reply .= $got;
	}
	return wantarray ? ($reply, $errs) : $reply;
    }


    # end of package SSL_Handle
}

# ftp_get:

sub ftp_get {
    my($is_dir, $rcode, @r, $dataport, $remote_addr,
       $ext, $content_type, %content_type, $content_length, $enc_URL,
       @welcome, @cwdmsg) ;
    local($/)= "\012" ;

    $port= 21 if $port eq '' ;

    
    %content_type=
	  ('txt',  'text/plain',
	   'text', 'text/plain',
	   'htm',  'text/html',
	   'html', 'text/html',
	   'css',  'text/css',
	   'png',  'image/png',
	   'jpg',  'image/jpeg',
	   'jpeg', 'image/jpeg',
	   'jpe',  'image/jpeg',
	   'gif',  'image/gif',
	   'xbm',  'image/x-bitmap',
	   'mpg',  'video/mpeg',
	   'mpeg', 'video/mpeg',
	   'mpe',  'video/mpeg',
	   'qt',   'video/quicktime',
	   'mov',  'video/quicktime',
	   'aiff', 'audio/aiff',
	   'aif',  'audio/aiff',
	   'au',   'audio/basic',
	   'snd',  'audio/basic',
	   'wav',  'audio/x-wav',
	   'mp2',  'audio/x-mpeg',
	   'mp3',  'audio/mpeg',
	   'ram',  'audio/x-pn-realaudio',
	   'rm',   'audio/x-pn-realaudio',
	   'ra',   'audio/x-pn-realaudio',
	   'gz',   'application/x-gzip',
	   'zip',  'application/zip',
	   ) ;


    $is_dir= $path=~ m#/$# ;
    $is_html= 0 if $is_dir ;   # for our purposes, do not treat dirs as HTML

    ($ext)= $path=~ /\.(\w+)$/ ;  # works for FTP, not for URLs with query etc.
    $content_type= ($is_html || $is_dir)  ? 'text/html'
					  : $content_type{lc($ext)}
					    || 'text/plain' ;


    if ($scripts_are_banned_here) {
	&script_content_die if $content_type=~ /^$SCRIPT_TYPE_REGEX$/io ;
    }

    local($path)= $path ;
    $path=~ s/%20/ /g ;

    $status= "$HTTP_1_X 200 OK\015\012" ;
    $headers= $NO_CACHE_HEADERS . "Date: " . &rfc1123_date($now,0) . "\015\012"
	. ($content_type  ? "Content-type: $content_type\015\012"  : '') . "\015\012" ;


    # Open the control connection to the FTP server
    &newsocketto('S', $host, $port) ;
    binmode S ;   # see note with "binmode STDOUT", above

    # Luckily, RFC 959 (FTP) has a really good list of all possible response
    #   codes to all possible commands, on pages 50-53.

    # Connection establishment
    ($rcode)= &ftp_command('', '120|220') ;
    &ftp_command('', '220') if $rcode==120 ;

    # Login
    ($rcode, @welcome)= &ftp_command("USER $username\015\012", '230|331') ;
    ($rcode, @welcome)= &ftp_command("PASS $password\015\012", '230|202')
	if $rcode==331 ;

    # Set transfer parameters
    &ftp_command("TYPE I\015\012", '200') ;


    if ($USE_PASSIVE_FTP_MODE) {
	my(@p) ;
	($rcode, @r)= &ftp_command("PASV\015\012", '227') ;
	@p= (join('',@r))=~
		/(\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)/ ;
	$dataport= ($p[4]<<8) + $p[5] ;

	&newsocketto('DATA_XFER', $host, $dataport) ;
	binmode DATA_XFER ;   # see note with "binmode STDOUT", above

    # If not using passive FTP, listen on open port and send PORT command.
    # See notes by newsocketto() about replacing pack('S n a4 x8') usage.
    } else {
	# Create and listen on data socket
	socket(DATA_LISTEN, AF_INET, SOCK_STREAM, (getprotobyname('tcp'))[2])
	    || &HTMLdie("Couldn't create FTP data socket: $!") ;
#	bind(DATA_LISTEN, pack('S n a4 x8', AF_INET, 0, "\0\0\0\0") )
	bind(DATA_LISTEN, pack_sockaddr_in(0, INADDR_ANY))
	    || &HTMLdie("Couldn't bind FTP data socket: $!") ;
#	$dataport= (unpack('S n a4 x8', getsockname(DATA_LISTEN)))[1] ;
	$dataport= (unpack_sockaddr_in(getsockname(DATA_LISTEN)))[0] ;
	listen(DATA_LISTEN,1)
	    || &HTMLdie("Couldn't listen on FTP data socket: $!") ;
	select((select(DATA_LISTEN), $|=1)[0]) ;    # unbuffer the socket

	# Tell FTP server which port to connect to
	&ftp_command( sprintf("PORT %d,%d,%d,%d,%d,%d\015\012",
			      unpack('C4', substr(getsockname(S),4,4)),
			      $dataport>>8, $dataport & 255),
		      '200') ;
    }
    if ($is_dir) {
	# If we don't CWD first, then symbolic links won't be followed.
	($rcode, @cwdmsg)= &ftp_command("CWD $path\015\012", '250') ;
	($rcode, @r)= &ftp_command("LIST\015\012", '125|150') ;
# was:  ($rcode, @r)= &ftp_command("LIST $path\015\012", '125|150') ;

    } else {
	($rcode, @r)= &ftp_command("RETR $path\015\012", '125|150|550') ;
	if ($rcode==550) {
	    ($rcode)= &ftp_command("CWD $path\015\012", '') ;
	    &ftp_error(550,@r) unless $rcode==250 ;

	    ($enc_URL= $URL)=~ s/ /%20/g ;  # URL-encode any spaces

	    # Redirect the browser to the same URL with a trailing slash
	    print "$HTTP_1_X 301 Moved Permanently\015\012", $NO_CACHE_HEADERS,
		  "Date: ", &rfc1123_date($now,0), "\015\012",
		  "Location: ", $url_start, &wrap_proxy_encode($enc_URL . '/'),
		  "\015\012\015\012" ;
	    close(S) ; close(DATA_LISTEN) ; close(DATA_XFER) ;
	    goto EXIT ;
	}
    }


    # If not using passive FTP, accept the connection.
    if (!$USE_PASSIVE_FTP_MODE) {
	($remote_addr= accept(DATA_XFER, DATA_LISTEN))
	    || &HTMLdie("Error accepting FTP data socket: $!") ;
	select((select(DATA_XFER), $|=1)[0]) ;      # unbuffer the socket
	close(DATA_LISTEN) ;
	&HTMLdie("Intruder Alert!  Someone other than the server is trying "
	       . "to send you data.")
	    unless (substr($remote_addr,4,4) eq substr(getpeername(S),4,4)) ;
    }

    if ( !$is_dir && !$is_html &&
	 (    ($expected_type=~ /^$TYPES_TO_HANDLE_REGEX$/io)
	   || ($content_type=~  /^$TYPES_TO_HANDLE_REGEX$/io)   ) ) {

	my($type) ;
	if ( ($expected_type eq 'text/css') || ($content_type eq 'text/css') ) {
	    $type= 'text/css' ;
	} elsif ($expected_type=~ /^$TYPES_TO_HANDLE_REGEX$/io) {
	    $type= $expected_type ;
	} else {
	    $type= $content_type ;
	}

	undef $/ ;
	$body= <DATA_XFER> ;
	$body= &proxify_block($body, $type) ;

	$headers= "Content-Length: " . length($body) . "\015\012" . $headers ;

	print $status, $headers, $body ;
	$response_sent= 1 ;


    } elsif ($is_html) {
	undef $/ ;
	$body= <DATA_XFER> ;

    } elsif ($is_dir) {
	undef $/ ;            # This was used for all non-HTML before streaming
	$body= <DATA_XFER> ;  #   was supported.

    } else {
	foreach (grep(/^(125|150)/, @r)) {
	    if ( ($content_length)= /\((\d+)[ \t]+bytes\)/ ) {
		$headers= "Content-Length: $content_length\015\012" .$headers ;
		last ;
	    }
	}

	# This is the primary change to support streaming media.
	my($buf) ;
	print $status, $headers ;
	print $buf while read(DATA_XFER, $buf, 16384) ;
	$response_sent= 1 ;

    }


    close(DATA_XFER) ;

    # Get the final completion response
    &ftp_command('', '226|250') ;

    &ftp_command("QUIT\015\012") ;   # don't care how they answer

    close(S) ;

    # Make a user-friendly directory listing.  Add Content-Length: header.
    if ($is_dir) {
	&ftp_dirfix(\@welcome, \@cwdmsg) ;
	$headers= "Content-Length: " . length($body) . "\015\012" . $headers ;
    }

}  # sub ftp_get()

sub ftp_command {
    my($cmd, $ok_response)= @_ ;
    my(@r, $rcode) ;
    local($/)= "\012" ;

    print S $cmd ;

    $_= $r[0]= <S> ;
    $rcode= substr($r[0],0,3) ;
    until (/^$rcode /) {      # this catches single- and multi-line responses
	push(@r, $_=<S>) ;
    }

    &ftp_error($rcode,@r) if $ok_response ne '' && $rcode!~ /$ok_response/ ;
    return $rcode, @r ;
}

sub ftp_dirfix {
    my($welcome_ref, $cwdmsg_ref)= @_ ;
    my($newbody, $parent_link, $max_namelen,
       @f, $is_dir, $is_link, $link, $name, $size, $size_type, $file_type,
       $welcome, $cwdmsg, $insertion, $enc_path) ;

    # Set minimum name column width; longer names will widen the column
    $max_namelen= 16 ;

    # each file should have name/, size, date
    my(@body)= split(/\015?\012/, $body) ;
    foreach (@body) {
	# Hack to handle leading spaces in filenames-- only allow a single
	#   space after the 8th field before filename starts.
#	@f= split(" ", $_, 9) ;   # Note special use of " " pattern.
#	next unless $#f>=8 ;
	@f= split(" ", $_, 8) ;   # Note special use of " " pattern.
	next unless $#f>=7 ;
	@f[7,8]= $f[7]=~ /^(\S*) (.*)/ ;  # handle leading spaces in filenames

	next if $f[8]=~ /^\.\.?$/ ;
	$file_type= '' ;
	$is_dir=  $f[0]=~ /^d/i ;
	$is_link= $f[0]=~ /^l/i ;
	$file_type= $is_dir     ? 'Directory'
		  : $is_link    ? 'Symbolic link'
		  :               '' ;
	$name= $f[8] ;
	$name=~ s/^(.*) ->.*$/$1/ if $is_link ;   # remove symlink's " -> xxx"
	$name.= '/' if $is_dir ;
	$max_namelen= length($name) if length($name)>$max_namelen ;
	if ($is_dir || $is_link) {
	    ($size, $size_type)= () ;
	} else {
	    ($size, $size_type)= ($f[4], 'bytes') ;
	    ($size, $size_type)= ($size>>10, 'Kb') if $size > 10240 ;
	}

	# Easy absolute URL calculation, because we know it's a relative path.
	($enc_path= $base_path . $name)=~ s/ /%20/g ;  # URL-encode any spaces
	$link=  &HTMLescape( $url_start . &wrap_proxy_encode($enc_path) ) ;

	$newbody.=
	    sprintf("  <a href=\"%s\">%s</a>%s %5s %-5s %3s %2s %5s  %s\012",
			   $link, $name, "\0".length($name),
			   $size, $size_type,
			   @f[5..7],
			   $file_type) ;
    }

    # A little hack to get filenames to line up right-- replace embedded
    #  "\0"-plus-length with correct number of spaces.
    $newbody=~ s/\0(\d+)/ ' ' x ($max_namelen-$1) /ge ;

    if ($path eq '/') {
	$parent_link= '' ;
    } else {
	($enc_path= $base_path)=~ s#[^/]*/$## ;
	$enc_path=~ s/ /%20/g ;  # URL-encode any spaces
	$link=  &HTMLescape( $url_start . &wrap_proxy_encode($enc_path) ) ;
	$parent_link= "<a href=\"$link\">Up to higher level directory</a>" ;
    }

    if ($SHOW_FTP_WELCOME && $welcome_ref) {
	$welcome= &HTMLescape(join('', grep(s/^230-//, @$welcome_ref))) ;
	# Make links of any URLs in $welcome.  Imperfect regex, but does OK.
	$welcome=~ s#\b([\w+.-]+://[^\s"']+[\w/])(\W)#
	    '<a href="' . &full_url($1) . "\">$1</a>$2" #ge ;
	$welcome.= "<hr>" if $welcome ne '' ;
    } else {
	$welcome= '' ;
    }

    # If CWD returned a message about this directory, display it.  Make links
    #   a la $welcome, above.
    if ($cwdmsg_ref) {
	$cwdmsg= &HTMLescape(join('', grep(s/^250-//, @$cwdmsg_ref))) ;
	$cwdmsg=~ s#\b([\w+.-]+://[^\s"']+[\w/])(\W)#
	    '<a href="' . &full_url($1) . "\">$1</a>$2" #ge ;
	$cwdmsg.= "<hr>" if $cwdmsg ne '' ;
    }


    # Create the top insertion if needed.
    $insertion= &full_insertion($URL,0)  if $doing_insert_here ;


    $body= <<EOS ;
<html>
<title>FTP directory of $URL</title>
<body>
$insertion
<h1>FTP server at $host</h1>
<h2>Current directory is $path</h2>
<hr>
<pre>
$welcome$cwdmsg
$parent_link
$newbody
</pre>
<hr>
</body>
</html>
EOS

}


# Return a generalized FTP error page.
# For now, respond with 200.  In the future, give more appropriate codes.
sub ftp_error {
    my($rcode,@r)= @_ ;

    close(S) ; close(DATA_LISTEN) ; close(DATA_XFER) ;

    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOH ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>FTP Error</title></head>
<body>
<h1>FTP Error</h1>
<h3>The FTP server at $host returned the following error response:</h3>
<pre>
EOH
    print @r, "</pre>\n" ;

    &footer ;
    goto EXIT ;
}



sub http_fix {
    my($name, $value, $new_value) ;
    my(@headers)= $headers=~ /^([^\012]*\012?)/mg ;  # split into lines

    foreach (@headers) {
	next unless ($name, $value)= /^([\w.-]+):\s*([^\015\012]*)/ ;
	$new_value= &new_header_value($name, $value) ;
	$_= defined($new_value)
	    ? "$name: $new_value\015\012"
	    : '' ;
    }

    $headers= join('', @headers) ;
}

sub new_header_value {
    my($name, $value)= @_ ;
    $name= lc($name) ;
    return undef if $name eq '' ;
    return &full_url($value)
	if    $name eq 'content-base'
	   || $name eq 'content-location' ;

    if ($name eq 'location') {
	local($url_start)= $script_url . '/' . $packed_flags . '/' ;
	return &full_url($value) ;
    }


    if ($name eq 'set-cookie') {
	return undef if $cookies_are_banned_here ;
	if ($NO_COOKIE_WITH_IMAGE || $e_filter_ads) {
	    return undef
		if ($headers=~ m#^Content-Type:\s*(\S*)#mi  &&  $1!~ m#^text/#i)
		   || ! grep(m#^(text|\*)/#i, split(/\s*,\s*/, $env_accept)) ;
	}

	return &cookie_to_client($value, $path, $host) ;
    }
    if ($name eq 'content-style-type') {
	$default_style_type= lc($1)  if $value=~ m#^\s*([/\w.+\$-]+)# ;
	return $value ;
    }
    if ($name eq 'content-script-type') {
	$default_script_type= lc($1)  if $value=~ m#^\s*([/\w.+\$-]+)# ;
	return $value ;
    }
    if ($name eq 'p3p') {
	$value=~ s/\bpolicyref\s*=\s*['"]?([^'"\s]*)['"]?/
		   'policyref="' . &full_url($1) . '"' /gie ;
	return $value ;
    }
    $value=~ s/(;\s*URL\s*=)\s*(\S*)/ $1 . &full_url($2) /ie,   return $value
	if $name eq 'refresh' ;

    # The deprecated URI: header may contain several URI's, inside <> brackets.
    $value=~ s/<(\s*[^>\015\012]*)>/ '<'.&full_url($1).'>' /gie, return $value
	if $name eq 'uri' ;


    if ($name eq 'link') {
	my($v, @new_values) ;

	my(@values)= $value=~ /(<[^>]*>[^,]*)/g ;
	foreach $v (@values) {
	    my($type)= $v=~ m#[^\w.\/?&-]type\s*=\s*["']?\s*([/\w.+\$-]+)#i ;
	    $type= lc($type) ;

	    if ($type eq '') {
		my($rel) ;
		$rel= $+
		    if $v=~ /[^\w.\/?&-]rel\s*=\s*("([^"]*)"|'([^']*)'|([^'"][^\s]*))/i ;
		$type= 'text/css' if $rel=~ /\bstylesheet\b/i ;
	    }

	    return undef
		if $scripts_are_banned_here && $type=~ /^$SCRIPT_TYPE_REGEX$/io ;

	    local($url_start)= $url_start ;
	    if ($type ne '') {
		$url_start= $script_url . '/' .
		    &pack_flags($e_remove_cookies, $e_remove_scripts, $e_filter_ads,
				$e_hide_referer, $e_insert_entry_form,
				$is_in_frame, $type)
			. '/' ;
	    }

	    $v=~ s/<(\s*[^>\015\012]*)>/ '<' . &full_url($1) . '>' /gie ;
	    push(@new_values, $v) ;
	}

	return join(', ', @new_values) ;
    }



    # For all non-special headers, return $value
    return $value ;

}

sub xproxy {
    my($URL)= @_ ;
    $URL=~ s/^x-proxy://i ;

    # $qs will contain the query string in $URL, whether it was encoded with
    #   the URL or came from QUERY_STRING.
    my($family, $function, $qs)=  $URL=~ m#^//(\w+)(/?[^?]*)\??(.*)#i ;

    if ($family eq 'auth') {

	# For //auth/make_auth_cookie, return an auth cookie and redirect user
	#   to the desired URL.  The URL is already encoded in $in{'l'}.
	if ($function eq '/make_auth_cookie') {
	    my(%in)= &getformvars() ; # must use () or will pass current @_!
	    my($location)= $url_start . $in{'l'} ;  # was already encoded
	    my($cookie)= &auth_cookie(@in{'u', 'p', 'r', 's'}) ;

	    &redirect_to($location, "Set-Cookie: $cookie\015\012") ;
	}


    } elsif ($family eq 'start') {
	&startproxy ;


    } elsif ($family eq 'cookies') {
	if ($function eq '/clear') {
	    my($location)=
		$url_start . &wrap_proxy_encode('x-proxy://cookies/manage') ;
	    $location.= '?' . $qs    if $qs ne '' ;

	    &redirect_to($location, &cookie_clearer($ENV{'HTTP_COOKIE'})) ;


	} elsif ($function eq '/manage') {
	    &manage_cookies($qs) ;


	# For //cookies/update, clear selected cookies and go to manage screen.
	} elsif ($function eq '/update') {
	    my(%in)= &getformvars() ; # must use () or will pass current @_!
	    my($location)=
		$url_start . &wrap_proxy_encode('x-proxy://cookies/manage') ;

	    # Add encoded "from" parameter to URL if available.
	    if ($in{'from'} ne '') {
		my($from_param)= $in{'from'} ;
		$from_param=~ s/([^\w.-])/ '%' . sprintf('%02x',ord($1)) /ge ;
		$location.=  '?from=' . $from_param ;
	    }

	    # "delete=" input fields are in form &base64(&cookie_encode($name)).
	    my(@cookies_to_delete) ;
	    foreach ( split(/\0/, $in{'delete'}) ) {
		push(@cookies_to_delete, &unbase64($_)) ; # use map{} in Perl 5
	    }

	    &redirect_to($location, &cookie_clearer(@cookies_to_delete)) ;
	}


    } elsif ($family eq 'frames') {
	my(%in)= &getformvars($qs) ;

	# Send the top proxy frame when a framed page is reframed.
	if ($function eq '/topframe') {
	    &return_top_frame($in{'URL'}) ;

	# Not currently used
	} elsif ($function eq '/framethis') {
	    &return_frame_doc($in{'URL'}, &HTMLescape(&wrap_proxy_decode($in{'URL'}))) ;
	}


    } elsif ($family eq 'scripts') {

	# Return the library needed for JavaScript rewriting.  Normally, this
	#   can be cached.
	if ($function eq '/jslib') {
	    &return_jslib ;
	}
    }
    &HTMLdie("Sorry, no such function as //". &HTMLescape("$family$function."),
	     '', '404 Not Found') ;

}

sub startproxy {
    my(%in)= &getformvars() ;  # must use () or will pass current @_!

    # Decode URL if it was encoded before transmission.
    $in{'URL'}= &wrap_proxy_decode($in{'URL'})
	if $ENCODE_URL_INPUT && $in{'URL'}=~ s/^\x01// ;

    $in{'URL'}=~ s/^\s+|\s+$//g ;    # strip leading or trailing spaces

    &show_start_form('Enter the URL you wish to visit in the box below.')
	if $in{'URL'} eq '' ;

    # Handle (badly) the special case of "mailto:" URLs, which don't have "://".
    &unsupported_warning($in{URL}) if $in{URL}=~ /^mailto:/i ;
    my($scheme, $authority, $path, $query, $fragment)=
	$in{URL}=~ m{^(?:([^:/?#]+)://)?([^/?#]*)([^?#]*)(\?[^#]*)?(#.*)?$} ;
    $scheme= lc($scheme) ;
    $path= '/' if $path eq '' ;

    # Parse $authority into username/password, hostname, and port-string.
    my($auth, $host, $portst)= $authority=~ /^([^@]*@)?([^:@]*)(:[^@]*)?$/ ;

    &show_start_form('The URL you entered has an invalid host name.', $in{URL})
	if !defined($host) ;

    $host= lc($host) ;   # must be after testing defined().

    &show_start_form('The URL must contain a valid host name.', $in{URL})
	if $host eq '' ;

    # Scheme defaults to FTP if host begins with "ftp.", else to HTTP.
    $scheme= ($host=~ /^ftp\./i)  ? 'ftp'  : 'http'   if $scheme eq '' ;

    &show_start_form('Sorry, only HTTP and FTP are currently supported.', $in{URL})
	unless $scheme=~ /^(http|https|ftp|x-proxy)$/ ;

    # Convert integer hostnames like 3467251275 to a.b.c.d format.
    # This is for big-endian; reverse the list for little-endian.
    $host= join('.', $host>>24 & 255, $host>>16 & 255, $host>>8 & 255, $host & 255)
	if $host=~ /^\d+$/ ;

    # Allow shorthand for hostnames-- if no "." is in it, then add "www"+"com"
    #   or "ftp"+"com".  Don't do it if the host already exists on the LAN.
    if ($scheme eq 'http') {
	$host= "www.$host.com"  if ($host!~ /\./) && !gethostbyname($host) ;
    } elsif ($scheme eq 'ftp') {
	# If there's username/password embedded (which you REALLY shouldn't do),
	#   then don't risk sending that to an unintended host.
	$host= "ftp.$host.com"
	    if ($auth eq '') && ($host!~ /\./) && !gethostbyname($host) ;
    }

    # Force $portst to ":" followed by digits, or ''.
    ($portst)= $portst=~ /^(:\d+)/ ;

    # Reassemble $authority after all changes are complete.
    $authority= $auth . $host . $portst ;

    # Prepend flag segment of PATH_INFO
    # This "erroneously" sets flags to "000000" when user config is not
    #   allowed, but it doesn't really affect anything.
    $url_start=~ s#[^/]*/$## ;   # remove old flag segment from $url_start
    $url_start.= &pack_flags(@in{'rc', 'rs', 'fa', 'br', 'if'}, $is_in_frame, '') . '/' ;

    &redirect_to( $url_start . &wrap_proxy_encode("$scheme://$authority$path$query") . $fragment ) ;
}


sub pack_flags {
    my($remove_cookies, $remove_scripts, $filter_ads, $hide_referer,
	  $insert_entry_form, $is_in_frame, $expected_type)= @_ ;
    my($flags) ;

    # Force all values to boolean for this format.
    $flags=   $remove_cookies     ? 1  : 0  ;
    $flags.=  $remove_scripts     ? 1  : 0  ;
    $flags.=  $filter_ads         ? 1  : 0  ;
    $flags.=  $hide_referer       ? 1  : 0  ;
    $flags.=  $insert_entry_form  ? 1  : 0  ;
    $flags.=  $is_in_frame        ? 1  : 0  ;

    # Add MIME type flag, packed into one character.
    $expected_type= pack('C', $MIME_TYPE_ID{lc($expected_type)}) ;
    $expected_type=~ tr#\x00-\x3f#A-Za-z0-9+-# ; # almost same as base64 chars

    $flags.=  $expected_type ;

    return $flags ;
}

sub unpack_flags {
    my($flags)= @_ ;
    my($remove_cookies, $remove_scripts, $filter_ads, $hide_referer,
       $insert_entry_form, $is_in_frame, $expected_type) ;

    ($remove_cookies, $remove_scripts, $filter_ads, $hide_referer,
     $insert_entry_form, $is_in_frame, $expected_type)=
	split(//, $flags) ;

    # Force all flags to valid values (currently all are 1 or 0).
    $remove_cookies=    $remove_cookies     ? 1  : 0  ;
    $remove_scripts=    $remove_scripts     ? 1  : 0  ;
    $filter_ads=        $filter_ads         ? 1  : 0  ;
    $hide_referer=      $hide_referer       ? 1  : 0  ;
    $insert_entry_form= $insert_entry_form  ? 1  : 0  ;
    $is_in_frame=       $is_in_frame        ? 1  : 0  ;

    # Extract expected MIME type from final one-character flag
    $expected_type=~ tr#A-Za-z0-9+-#\x00-\x3f# ;
    $expected_type= $ALL_TYPES[unpack('C', $expected_type)] ;

    return ($remove_cookies, $remove_scripts, $filter_ads, $hide_referer,
	    $insert_entry_form, $is_in_frame, $expected_type) ;
}


sub parse_cookie {
    my($cookie, $target_path, $target_server, $target_scheme)= @_ ;
    my($name, $value, $type, @n,
       $cname, $path, $domain, $cvalue, $secure, @matches, %pathlen,
       $realm, $server, @auth) ;

    foreach ( split(/\s*;\s*/, $cookie) ) {
	($name, $value)= split(/=/, $_, 2) ;     # $value may contain "="
	$name= &cookie_decode($name) ;
	$value= &cookie_decode($value) ;
	($type, @n)= split(/;/, $name) ;
	if ($type eq 'COOKIE') {
	    ($cname, $path, $domain)= @n ;
	    $domain= lc($domain) ;
	    ($cvalue, $secure)= split(/;/, $value) ;
	    next if $secure && ($target_scheme ne 'https') ;

	    if ( ($target_server=~ /\Q$domain\E$/i or (lc('.'.$target_server) eq lc($domain)) )
		 && $target_path=~ /^\Q$path\E/ )
	    {
		
		push(@matches, ($cname ne '' ? $cname.'='.$cvalue : $cvalue)) ;
		$pathlen{$matches[$#matches]}= length($path) ;
	    }
	} elsif ($type eq 'AUTH') {
	    # format of auth cookie's name is AUTH;$enc_realm;$enc_server
	    ($realm, $server)= @n ;
	    $realm=~  s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	    $server=~ s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	    push(@auth, $realm, $value)  if  $server eq $target_server ;
	}
    }

    # More specific path mappings (i.e. longer paths) should be sent first.
    $cookie= join('; ', sort { $pathlen{$b} <=> $pathlen{$a} } @matches) ;

    return $cookie, @auth ;
}
sub cookie_to_client {
    my($cookie, $source_path, $source_server)= @_ ;
    my($name, $value, $expires_clause, $path, $domain, $secure_clause) ;
    my($new_name, $new_value, $new_cookie) ;

    # Start last four regexes with ";" to avoid extracting from name=value.
    ($name, $value)=   $cookie=~ /^\s*([^=;,\s]*)\s*=?\s*([^;,\s]*)/ ;
    ($expires_clause)= $cookie=~ /;\s*(expires\s*=[^;]*)/i ;
    ($path)=     $cookie=~ /;\s*path\s*=\s*([^;,\s]*)/i ;  # clash w/ ;-params?
    ($domain)=         $cookie=~ /;\s*domain\s*=\s*([^;,\s]*)/i ;
    ($secure_clause)=  $cookie=~ /;\s*(secure\b)/i ;
    $path=  $COOKIE_PATH_FOLLOWS_SPEC  ? $source_path  : '/'  if $path eq '' ;

    if ($domain eq '') {
	$domain= $source_server ;
    } else {
	$domain=~ s/\.*$//g ;  # removes trailing dots!
	$domain=~ tr/././s ;   # ... and double dots for good measure.
	# Allow $domain to match domain-minus-leading-dot (erroneously),
	#   because that's how browsers do it.
	return undef
	    if ($source_server!~ /\Q$domain\E$/) and ('.'.$source_server ne $domain) ;
	if ($RESPECT_THREE_DOT_RULE) {
	    return(undef) unless
		( ( ($domain=~ tr/././) >= 3 ) ||
		  ( ($domain=~ tr/././) >= 2 &&
		    $domain=~ /\.(com|edu|net|org|gov|mil|int)$/i )
		) ;
	} else {
	    if (($domain=~ tr/././) < 2) {
		return undef  if $domain=~ /^\./ ;
		$domain= '.' . $domain ;
		return undef  if ($domain=~ tr/././) < 2 ;
	    }
	}
    }
    $new_name= &cookie_encode("COOKIE;$name;$path;$domain") ;

    # New value is "$value;$secure_clause", then cookie_encode()'d.
    $new_value= &cookie_encode("$value;$secure_clause") ;

    if ($SESSION_COOKIES_ONLY && $expires_clause ne '') {
	my($expires_date)= $expires_clause=~ /^expires\s*=\s*(.*)$/i ;
	$expires_clause= ''  if &date_is_after($expires_date, $now) ;
    }

    $new_cookie= join('; ', grep(length,
				 $new_name . '=' . $new_value,
				 $expires_clause,
				 'path=' . $ENV_SCRIPT_NAME . '/',
				 ($RUNNING_ON_SSL_SERVER ? ('secure') : () )
		     )) ;
    return $new_cookie ;

}
sub auth_cookie {
    my($username, $password, $realm, $server)= @_ ;

    $realm=~ s/(\W)/ '%' . sprintf('%02x',ord($1)) /ge ;
    $server=~ s/(\W)/ '%' . sprintf('%02x',ord($1)) /ge ;

    return join('', &cookie_encode("AUTH;$realm;$server"), '=',
		    &cookie_encode(&base64("$username:$password")),
		    '; path=' . $ENV_SCRIPT_NAME . '/' ) ;

}

sub cookie_clearer {
    my(@cookies)= @_ ;   # may be one or more lists of cookies
    my($ret, $cname) ;

    foreach (@cookies) {
	foreach $cname ( split(/\s*;\s*/) ) {
	    $cname=~ s/=.*// ;      # change "name=value" to "name"
	    $ret.= "Set-Cookie: $cname=; expires=Thu, 01-Jan-1970 00:00:01 GMT; "
		 . "path=$ENV_SCRIPT_NAME/\015\012" ;
	}
    }
    return $ret ;
}

sub newsocketto {
    my($S, $host, $port)= @_ ;
    my($hostaddr, $remotehost) ;

    # If $host is long integer like 3467251275, break it into a.b.c.d format.
    # This is for big-endian; reverse the list for little-endian.
    $host= join('.', $host>>24 & 255, $host>>16 & 255, $host>>8 & 255,
		     $host & 255)
	if $host=~ /^\d+$/ ;

    # Create the remote host data structure, from host name or IP address.
    # Note that inet_aton() handles both alpha names and IP addresses.
    $hostaddr= inet_aton($host)
	|| &HTMLdie("Couldn't find address for $host: $!") ;
#    $remotehost= pack('S n a4 x8', AF_INET, $port, $hostaddr) ;
    $remotehost= pack_sockaddr_in($port, $hostaddr) ;
    for (@BANNED_NETWORK_ADDRS) {
	&banned_server_die() if $hostaddr=~ /^$_/ ;   # No URL forces a die
    }

    # Create the socket and connect to the remote host
    no strict 'refs' ;   # needed to use $S as filehandle
    socket($S, AF_INET, SOCK_STREAM, (getprotobyname('tcp'))[2])
	|| &HTMLdie("Couldn't create socket: $!") ;
    connect($S, $remotehost)
	|| &HTMLdie("Couldn't connect to $host:$port: $!") ;
    select((select($S), $|=1)[0]) ;      # unbuffer the socket
}

sub read_socket {
#    local(*S, $length)= @_ ;
    my($S, $length)= @_ ;
    my($ret, $numread, $thisread) ;

    #$numread= 0 ;
    no strict 'refs' ;   # needed to use $S as filehandle

    while (    ($numread<$length)
#	    && ($thisread= read(S, $ret, $length-$numread, $numread) ) )
	    && ($thisread= read($S, $ret, $length-$numread, $numread) ) )
    {
	$numread+= $thisread ;
    }
    return undef unless defined($thisread) ;

    return $ret ;
}


sub get_chunked_body {
    my($S)= @_ ;
    my($body, $footers, $chunk_size, $chunk) ;
    local($_) ;
    local($/)= "\012" ;
    no strict 'refs' ;     # needed to use $S as filehandle
    $body= '' ;            # to distinguish it from undef
    while ($chunk_size= hex(<$S>) ) {
	$body.= $chunk= &read_socket($S, $chunk_size) ;
	return undef unless length($chunk) == $chunk_size ;  # implies defined()
	$_= <$S> ;         # clear CRLF after chunk
    }

    # After all chunks, read any footers, NOT including the final blank line.
    while (<$S>) {
	last if /^(\015\012|\012)/  || $_ eq '' ;   # lines end w/ LF or CRLF
	$footers.= $_ ;
    }
    $footers=~ s/(\015\012|\012)[ \t]+/ /g ;       # unwrap long footer lines

    return wantarray  ? ($body, $footers)  : $body  ;
}
sub getformvars {
    my($in)= @_ ;
    my(%in, $name, $value) ;

    # If no string is passed, read it from the usual channels.
    unless (defined($in)) {
	if ( ($ENV{'REQUEST_METHOD'} eq 'GET') ||
	     ($ENV{'REQUEST_METHOD'} eq 'HEAD') ) {
	    $in= $ENV{'QUERY_STRING'} ;
	} elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
	    return undef unless
		lc($ENV{'CONTENT_TYPE'}) eq 'application/x-www-form-urlencoded';
	    return undef unless defined($ENV{'CONTENT_LENGTH'}) ;
	    $in= &read_socket('STDIN', $ENV{'CONTENT_LENGTH'}) ;
	    # should we return undef if not all bytes were read?
	} else {
	    return undef ;   # unsupported REQUEST_METHOD
	}
    }

    foreach (split(/[&;]/, $in)) {
	s/\+/ /g ;
	($name, $value)= split('=', $_, 2) ;
	$name=~ s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	$value=~ s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	$in{$name}.= "\0" if defined($in{$name}) ;  # concatenate multiple vars
	$in{$name}.= $value ;
    }
    return %in ;
}

sub rfc1123_date {
    my($time, $use_dash)= @_ ;
    my($s) =  $use_dash  ? '-'  : ' ' ;
    my(@t)= gmtime($time) ;

    return sprintf("%s, %02d$s%s$s%04d %02d:%02d:%02d GMT",
		   $WEEKDAY[$t[6]], $t[3], $MONTH[$t[4]], $t[5]+1900, $t[2], $t[1], $t[0] ) ;
}

sub date_is_after {
    my($date1, $date2)= @_ ;
    my(@d1, @d2) ;

    # Trivial case when both are numeric.
    return ($date1>$date2)  if $date1=~ /^\d+$/ && $date2=~ /^\d+$/ ;

    # Get date components, depending on formats
    if ($date1=~ /^\d+$/) {
	@d1= (gmtime($date1))[3,4,5,2,1,0] ;
    } else {
	@d1= $date1=~ /^\w+,\s*(\d+)[ -](\w+)[ -](\d+)\s+(\d+):(\d+):(\d+)/ ;
	return undef unless @d1 ;
	$d1[1]= $UN_MONTH{lc($d1[1])} ;
	$d1[2]-= 1900 ;
    }
    if ($date2=~ /^\d+$/) {
	@d2= (gmtime($date2))[3,4,5,2,1,0] ;
    } else {
	@d2= $date2=~ /^\w+,\s*(\d+)[ -](\w+)[ -](\d+)\s+(\d+):(\d+):(\d+)/ ;
	return undef unless @d2 ;
	$d2[1]= $UN_MONTH{lc($d2[1])} ;
	$d2[2]-= 1900 ;
    }

    # Compare year, month, day, hour, minute, second in order.
    return ( ( $d1[2]<=>$d2[2] or $d1[1]<=>$d2[1] or $d1[0]<=>$d2[0] or
	       $d1[3]<=>$d2[3] or $d1[4]<=>$d2[4] or $d1[5]<=>$d2[5] )
	     > 0 ) ;
}

sub HTMLescape {
    my($s)= @_ ;
    $s=~ s/&/&amp;/g ;      # must be before all others
    $s=~ s/"/&quot;/g ;
    $s=~ s/</&lt;/g ;
    $s=~ s/>/&gt;/g ;
    return $s ;
}

sub HTMLunescape {
    my($s)= @_ ;
    $s=~ s/&quot\b;?/"/g ;
    $s=~ s/&lt\b;?/</g ;
    $s=~ s/&gt\b;?/>/g ;
    $s=~ s/&amp\b;?/&/g ;      # must be after all others
    return $s ;
}
sub base64 {
    my($s)= @_ ;
    my($ret, $p, @c, $t) ;
    while ($p<length($s)) {
	@c= unpack('C3', substr($s,$p,3)) ;
	$p+= 3 ;
	$t= ($c[0]<<16) + ($c[1]<<8) + $c[2] ;     # total 24-bit integer
	$ret.= pack('C4',     $t>>18,
			     ($t>>12)%64,
		    (@c>1) ? ($t>>6) %64 : 64,
		    (@c>2) ?  $t     %64 : 64 ) ;  # "@" is chr(64)
    }

    # Translate from bottom 64 chars into base64 chars, plus @ to = conversion.
    $ret=~ tr#\x00-\x3f@#A-Za-z0-9+/=# ;

    return $ret ;
}


# Opposite of base64() .
sub unbase64 {
    my($s)= @_ ;
    my($ret, $p, @c, $t, $pad) ;

    $pad++ if $s=~ /=$/ ;
    $pad++ if $s=~ /==$/ ;

    $s=~ tr#A-Za-z0-9+/##cd ;          # remove non-allowed characters
    $s=~ tr#A-Za-z0-9+/#\x00-\x3f# ;   # for speed, translate to \x00-\x3f

    # For each 4 chars, build a 24-bit integer and split it into 8-bit bytes.
    # Remove one or two chars from result if input had padding chars.
    while ($p<length($s)) {
	@c= unpack('C4', substr($s,$p,4)) ;
	$p+= 4 ;
	$t= ($c[0]<<18) + ($c[1]<<12) + ($c[2]<<6) + $c[3] ;
	$ret.= pack('C3', $t>>16, ($t>>8) % 256, $t % 256 ) ;
    }
    chop($ret) if $pad>=1 ;
    chop($ret) if $pad>=2 ;

    return $ret ;
}

sub readfile {
    my($fname)= @_ ;
    my($ret) ;
    local(*F, $/) ;

    open(F, "<$fname") || return undef ;
    undef $/ ;
    $ret= <F> ;
    close(F) ;

    return $ret ;
}
sub http_get2 {
    my($c, $request_uri)= @_ ;
    my($s, $status, $status_code, $headers, $body, $footers, $rin, $win, $num_tries) ;
    local($/)= "\012" ;

    no strict 'refs' ;    # needed for symbolic references

    # Using "$c->{socket}" causes syntax errors in some places, so alias it to $s.
    $s= $c->{socket} ;

    RESTART: {
	if (!$c->{open} || !select(undef, $win, undef, 0)) {
	    &newsocketto($c->{socket}, $c->{host}, $c->{port}) ;
	    $c->{open}= 1 ;
	}

	# Print the simple request.
	print $s 'GET ', $request_uri, " HTTP/1.1\015\012",
		 'Host: ', $c->{host}, (($c->{port}==80)  ? ''  : ":$c->{port}"), "\015\012",
		 "\015\012" ;


	vec($rin= '', fileno($s), 1)= 1 ;
	select($rin, undef, undef, 60)
	    || &HTMLdie("No response from $c->{host}:$c->{port}") ;

	$status= <$s> ;

	# hack hack....
	unless ($status=~ m#^HTTP/#) {
	    $c->{open}= 0 ;
	    redo RESTART if ++$num_tries<3 ;
	    &HTMLdie("Invalid response from $c->{host}: [$status]") ;
	}
    }
    do {
	($status_code)= $status=~ m#^HTTP/\d+\.\d+\s+(\d+)# ;

	$headers= '' ;
	do {
	    $headers.= $_= <$s> ;    # $headers includes last blank line
	} until (/^(\015\012|\012)$/) || $_ eq '' ; #lines end w/ LF or CRLF

	$status= <$s> if $status_code == 100 ;  # re-read for next iteration
    } until $status_code != 100 ;

    # Unfold long header lines, a la RFC 822 section 3.1.1
    $headers=~ s/(\015\012|\012)[ \t]+/ /g ;

    # Read socket body depending on how length is determined; see RFC 2616 (the
    #   HTTP 1.1 spec), section 4.4.
    if ($headers=~ /^Transfer-Encoding:[ \t]*chunked\b/mi) {
	($body, $footers)= &get_chunked_body($s) ;
	&HTMLdie(&HTMLescape("Error reading chunked response from $c->{host} ."))
	    unless defined($body) ;
	$headers=~ s/^Transfer-Encoding:[^\012]*\012?//mig ;
	$headers=~ s/^(\015\012|\012)/$footers$1/m ;

    } elsif ($headers=~ /^Content-Length:[ \t]*(\d+)/mi) {
	$body= &read_socket($s, $1) ;

    } else {
	undef $/ ;
	$body= <$s> ;  # ergo won't be persistent connection
	close($s) ;
	$c->{open}= 0 ;
    }

    # If server doesn't support persistent connections, then close the socket.
    # We would test eof($s) here, but that causes a long wait.
    if ($headers=~ /^Connection:.*\bclose\b/mi || $status=~ m#^HTTP/1\.0#) {
	close($s) ;
	$c->{open}= 0 ;
    }

    return $body ;
}
sub full_insertion {
    my($URL, $in_top_frame)= @_ ;
    my($form, $insertion) ;
    $form= &mini_start_form($URL, $in_top_frame) if $e_insert_entry_form ;

    if (($INSERT_HTML ne '') || ($INSERT_FILE ne '')) {
	&set_custom_insertion if $CUSTOM_INSERTION eq '' ;
	if ($ANONYMIZE_INSERTION) {
	    local($base_url)= $script_url ;
	    &fix_base_vars ;
	    $insertion= &proxify_html(\$CUSTOM_INSERTION,0) ;
	} else {
	    $insertion= $CUSTOM_INSERTION ;
	}
    }

    return $FORM_AFTER_INSERTION   ? $insertion . $form   : $form . $insertion ;
}
sub set_custom_insertion {
    return if $CUSTOM_INSERTION ne '' ;
    return unless ($INSERT_HTML ne '') || ($INSERT_FILE ne '') ;

    # Read $CUSTOM_INSERTION from the appropriate source.
    $CUSTOM_INSERTION= ($INSERT_HTML ne '')   ? $INSERT_HTML  : &readfile($INSERT_FILE) ;

    # Now, set counts in %IN_CUSTOM_INSERTION.
    %IN_CUSTOM_INSERTION= () ;
    foreach (qw(applet embed form id layer)) {
	$IN_CUSTOM_INSERTION{$_.'s'}++ while $CUSTOM_INSERTION=~ /<\s*$_\b/gi ;
    }
    $IN_CUSTOM_INSERTION{anchors}++ while $CUSTOM_INSERTION=~ /<\s*a\b[^>]*\bname\s*=/gi ;
    $IN_CUSTOM_INSERTION{links}++   while $CUSTOM_INSERTION=~ /<\s*a\b[^>]*\bhref\s*=/gi ;
    $IN_CUSTOM_INSERTION{images}++  while $CUSTOM_INSERTION=~ /<\s*img\b/gi ;
}



# Print the footer common to most error responses
sub footer {
    my($rightlink)= $NO_LINK_TO_START
	? ''
	: qq(<a href="$script_url"><i>Restart</i></a>) ;

    print <<EOF ;
<p>
<hr>
<table width="100%"><tr>
<td align=left>
<a href="http://www.jmarshall.com/tools/cgiproxy/"><i>CGIProxy 2.1beta9</i></a>
</td>
<td align=right>
$rightlink
</td>
</tr></table>
<p>
</body>
</html>
EOF
}
sub return_top_frame {
    my($enc_URL)= @_ ;
    my($body, $insertion) ;
    my($date_header)= &rfc1123_date($now, 0) ;
    local($base_unframes)= 1 ;
    local($url_start)= $url_start_noframe ;

    $body= &full_insertion(&wrap_proxy_decode($enc_URL), 1) ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-Type: text/html

<html>
<head><base target="_top"></head>
<body>
$body
</body>
</html>
EOF

    goto EXIT ;
}
sub return_frame_doc {
    my($enc_URL, $title)= @_ ;
    my($qs_URL, $top_URL, $page_URL) ;
    my($date_header)= &rfc1123_date($now, 0) ;

    ($qs_URL= $enc_URL) =~ s/([^\w.-])/ '%' . sprintf('%02x',ord($1)) /ge ;
    $top_URL= &HTMLescape($url_start_inframe
			. &wrap_proxy_encode('x-proxy://frames/topframe?URL=' . $qs_URL) ) ;
    $page_URL= &HTMLescape($url_start_inframe . $enc_URL) ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-Type: text/html

<html>
<head>
<title>$title</title>
</head>
<frameset rows="$INSERTION_FRAME_HEIGHT,*">
    <frame src="$top_URL">
    <frame src="$page_URL" name="_proxy_jslib_main_frame">
</frameset>
</html>
EOF

    goto EXIT ;
}



# When an image should be blanked, returns either a transparent 1x1 GIF or
#   a 406 result ("Not Acceptable").
sub skip_image {
    &return_empty_gif if $RETURN_EMPTY_GIF ;

    my($date_header)= &rfc1123_date($now, 0) ;
    print "$HTTP_1_X 406 Not Acceptable\015\012${NO_CACHE_HEADERS}Date: $date_header\015\012\015\012" ;
    goto EXIT ;
}


# Return a 1x1 transparent GIF.  Yes, that's an inlined 43-byte GIF.
sub return_empty_gif {
    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-Type: image/gif
Content-Length: 43

GIF89a\x01\0\x01\0\x80\0\0\0\0\0\xff\xff\xff\x21\xf9\x04\x01\0\0\0\0\x2c\0\0\0\0\x01\0\x01\0\x40\x02\x02\x44\x01\0\x3b
EOF

    goto EXIT ;
}
sub redirect_to {
    my($location, $other_headers)= @_ ;
    print "$HTTP_1_X 302 Moved\015\012", $NO_CACHE_HEADERS,
	  "Date: ", &rfc1123_date($now,0), "\015\012",
	  $other_headers,
	  "Location: $location\015\012\015\012" ;

    goto EXIT ;
}
sub show_start_form {
    my($msg, $URL)= @_ ;
    my($method, $action, $flags, $cookies_url, $safe_URL, $jslib_block,
       $onsubmit, $onload) ;
    my($date_header)= &rfc1123_date($now, 0) ;

    $msg= "\n<h1><font color=green>$msg</font></h1>"  if $msg ne '' ;

    $method= $USE_POST_ON_START   ? 'post'   : 'get' ;

    $action=      &HTMLescape( $url_start . &wrap_proxy_encode('x-proxy://start') ) ;
    $cookies_url= &HTMLescape( $url_start . &wrap_proxy_encode('x-proxy://cookies/manage') ) ;
    $safe_URL= &HTMLescape($URL) ;

    # Encode the URL before submitting, if so configured.  Start it with "\x01"
    #   to indicate that it's encoded ("\0" isn't handled well by all browsers).
    if ($ENCODE_URL_INPUT) {
	$jslib_block= '<script src="'
		    . &HTMLescape($url_start . &wrap_proxy_encode('x-proxy://scripts/jslib'))
		    . "\"></script>\n" ;
	$onsubmit= q( onsubmit="if (!document.URLform.URL.value.match(/^\\x01/)) document.URLform.URL.value= '\x01'+_proxy_jslib_wrap_proxy_encode(document.URLform.URL.value) ; return true") ;
	$onload= q( onload="if (document.URLform.URL.value.match(/^\\x01/)) document.URLform.URL.value= _proxy_jslib_wrap_proxy_decode(document.URLform.URL.value.replace(/\\x01/, ''))") ;
    } else {
	$jslib_block= $onsubmit= $onload= '' ;
    }

    # Include checkboxes if user config is allowed.
    if ($ALLOW_USER_CONFIG) {
	my($rc_on)= $e_remove_cookies     ? ' checked'  : '' ;
	my($rs_on)= $e_remove_scripts     ? ' checked'  : '' ;
	my($fa_on)= $e_filter_ads         ? ' checked'  : '' ;
	my($br_on)= $e_hide_referer       ? ' checked'  : '' ;
	my($if_on)= $e_insert_entry_form  ? ' checked'  : '' ;
	$flags= <<EOF ;
<br><input type=checkbox id="rc" name="rc"$rc_on><label for="rc"><font color="white"> Tanpa Kuki</font>)</label>
<input type=checkbox id="rs" name="rs"$rs_on><label for="rs"><font color="white"> Tanpa Script</font></label>
<input type=checkbox id="fa" name="fa"$fa_on><label for="fa"><font color="white"> Tanpa Iklan</font></label>
<input type=checkbox id="br" name="br"$br_on><label for="br"><font color="white"> Sembunyikan Info</font></label>
<input type=checkbox id="if" name="if"$if_on><label for="if"><font color="white"> Tampilkan URL</font></label>
EOF
    }

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head>
$jslib_block
<title>Selamat Datang di Bocah Newbie</title>
<link rel="shortcut icon" href="https://lh5.googleusercontent.com/DpRpLimw2GMrtvGIpHaqMzvbtbWZh72iOafAeFBIeZKB8L5xgOi8cqPafMdEJ-4zHevqNBk4bwk"></link>
<body style="background-image: url('https://doc-0o-94-docs.googleusercontent.com/docs/securesc/0cplnum47782ib8r2utt3qt2ioikmv1j/gv2fa5kinhbh92ijv4n7ou4i547voaav/1405252800000/11143202878355062340/11143202878355062340/0B6bPHR39IYCSc0RzU2N4eHFCX28?h=16653014193614665626&e'); background-repeat: repeat; background-position: center; background-attachment: fixed;">
</head>
<body$onload>
$msg
<center><h1><font color="white">Bocah Newbie CGIProxy</font></h1>
<img src="https://doc-0o-94-docs.googleusercontent.com/docs/securesc/0cplnum47782ib8r2utt3qt2ioikmv1j/58qb9vsaem7kg8vo805sfnook7gg2rnn/1405252800000/11143202878355062340/11143202878355062340/0B6bPHR39IYCSMU5FN1o1bnBYUE0?h=16653014193614665626&e" width=299px height=299px border="1" />
<p><font color="white">Internet itu buat buka yang positif, bukan ngakalin internet positif<br>Masukkan URL Website Tujuan</font>

<form name="URLform" action="$action" method=$method$onsubmit>
<input name="URL" size=66 value="$safe_URL">
$flags
<p><input type=submit value="   Tancap Gass   ">
</form>

<h3><a href="$cookies_url">Hapus Cookies</a></h3></center>
EOF

    goto EXIT ;
}

sub mini_start_form {
    my($URL, $in_top_frame)= @_ ;
    my($method, $action, $flags, $table_open, $table_close,
       $cookies_url, $from_param, $safe_URL, $onsubmit, $onfocus) ;

    $method= $USE_POST_ON_START   ? 'post'   : 'get' ;
    $action= &HTMLescape( $url_start_noframe . &wrap_proxy_encode('x-proxy://start') ) ;
    $safe_URL= &HTMLescape($URL) ;
    $from_param= &wrap_proxy_encode($URL) ;   # don't send unencoded URL
    $from_param=~ s/([^\w.-])/ '%' . sprintf('%02x',ord($1)) /ge ;
    $cookies_url= $url_start_noframe . &wrap_proxy_encode('x-proxy://cookies/manage')
		. '?from=' . $from_param ;
    $cookies_url= &HTMLescape($cookies_url) ;

    # Create "UP" link.
    my($scheme_authority, $up_path)= $URL=~ m{^([^:/?#]+://[^/?#]*)([^?#]*)} ;
    $up_path=~ s#[^/]*.$##s ;
    my($safe_up_URL)= &HTMLescape( $url_start_noframe . &wrap_proxy_encode("$scheme_authority$up_path") ) ;
    my($up_link)= $up_path ne ''
	? qq(&nbsp;&nbsp;<a href="$safe_up_URL" target="_top" style="color:#0000FF;">[&nbsp;Halaman Sebelumnya&nbsp;]</a>)
	: '' ;

    # Alter various HTML depending on whether we're in the top frame or not.
    ($table_open, $table_close)= $in_top_frame
	? ('', '')
	: ('<table border=1 cellpadding=5 color=white><tr><td align=center bgcolor=white><font color=black>',
	   '</font></td></tr></table>') ;
    %in_mini_start_form= ('forms', 1, 'links', (($up_path ne '')  ? 2  : 1)) ;
  
    if ($ENCODE_URL_INPUT) {
	$needs_jslib= 1 ;
	$onsubmit= q( onsubmit="if (!document.URLform.URL.value.match(/^\\x01/)) document.URLform.URL.value= '\x01'+_proxy_jslib_wrap_proxy_encode(document.URLform.URL.value) ; return true") ;
	$onfocus= q( onfocus="if (document.URLform.URL.value.match(/^\\x01/)) document.URLform.URL.value= _proxy_jslib_wrap_proxy_decode(document.URLform.URL.value.replace(/\\x01/, ''))") ;
    } else {
	$onsubmit= '' ;
    }


    # Display one of two forms, depending on whether user config is allowed.
    if ($ALLOW_USER_CONFIG) {
	my($rc_on)= $e_remove_cookies     ? ' checked'  : '' ;
	my($rs_on)= $e_remove_scripts     ? ' checked'  : '' ;
	my($fa_on)= $e_filter_ads         ? ' checked'  : '' ;
	my($br_on)= $e_hide_referer       ? ' checked'  : '' ;
	my($if_on)= $e_insert_entry_form  ? ' checked'  : '' ;

# jsm-- remove for production release, plus in form below.
my($safe_URL2) ;
($safe_URL2= $URL)=~ s/([^\w.-])/ '%' . sprintf('%02x',ord($1)) /ge ;
$safe_URL2= "http://jmarshall.com/report.cgi?URL=$safe_URL2" ;
$safe_URL2= &HTMLescape(&full_url($safe_URL2)) ;

	return <<EOF ;
<form name="URLform" action="$action" method=$method target="_top"$onsubmit>
<center>
$table_open
&nbsp;&nbsp;Alamat&nbsp;URL&nbsp;: <input name="URL" size=66 value="$safe_URL"$onfocus><input type=submit value="Lanjut">
$up_link&nbsp;&nbsp;
<br><a href="$safe_URL2"><font color=red>[Laporkan Bug]</font></a>
&nbsp;&nbsp;<a href="$cookies_url" target="_top" style="color:#0000FF;">[Pengaturan&nbsp;Kuki]</a>
</br>
$table_close
</center>
</form>
EOF

    # If user config isn't allowed, then show a different form.
    } else {
	return <<EOF ;
<form name="URLform" action="$action" method=$method target="_top"$onsubmit>
<center>
$table_open
Alamat URL : <input name="URL" size=66 value="$safe_URL"$onfocus><input type=submit value="Go">
$up_link
&nbsp;&nbsp;<a href="$cookies_url" target="_top">[Pengaturan Kuki]</a>
$table_close
</center>
</form>
EOF
    }

}
sub manage_cookies {
    my($qs)= @_ ;
    my($return_url, $action, $clear_cookies_url, $cookie_rows, $auth_rows,
       $from_tag) ;
    my($name, $value, $type, @n, $delete_cb,
       $cname, $path, $domain, $cvalue, $secure,
       $realm, $server, $username) ;

    my($date_header)= &rfc1123_date($now, 0) ;

    my(%in)= &getformvars($qs) ;

    # $in{'from'} is already proxy_encoded
    $return_url= &HTMLescape( $url_start . $in{'from'} ) ;
    $action=     &HTMLescape( $url_start . &wrap_proxy_encode('x-proxy://cookies/update') ) ;

    # Create "clear cookies" link, preserving any query string.
    $clear_cookies_url= $url_start . &wrap_proxy_encode('x-proxy://cookies/clear') ;
    $clear_cookies_url.= '?' . $qs    if $qs ne '' ;
    $clear_cookies_url= &HTMLescape($clear_cookies_url) ;   # probably never necessary

    # Include from-URL in form if it's available.
    $from_tag= '<input type=hidden name="from" value="' . &HTMLescape($in{'from'}) . '">'
	if $in{'from'} ne '';
    foreach ( split(/\s*;\s*/, $ENV{'HTTP_COOKIE'}) ) {
	($name, $value)= split(/=/, $_, 2) ;  # $value may contain "="
	$delete_cb= '<input type=checkbox name="delete" value="'
		  . &base64($name) . '">' ;
	$name= &cookie_decode($name) ;
	$value= &cookie_decode($value) ;
	($type, @n)= split(/;/, $name) ;
	if ($type eq 'COOKIE') {
	    ($cname, $path, $domain)= @n ;
	    ($cvalue, $secure)= split(/;/, $value) ;

	    $cookie_rows.= sprintf("<tr align=center><td>%s</td>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td></tr>\n",
				   $delete_cb,
				   &HTMLescape($domain),
				   &HTMLescape($path),
				   &HTMLescape($cname),
				   &HTMLescape($cvalue),
				   $secure ? 'Yes' : 'No',
				  ) ;

	} elsif ($type eq 'AUTH') {
	    # format of auth cookie's name is AUTH;$enc_realm;$enc_server
	    ($realm, $server)= @n ;
	    $realm=~  s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	    $server=~ s/%([\da-fA-F]{2})/ pack('C', hex($1)) /ge ;
	    ($username)= split(/:/, &unbase64($value)) ;

	    $auth_rows.= sprintf("<tr align=center><td>%s</td>\n<td>%s</td>\n<td>%s</td>\n<td>%s</td></tr>\n",
				 $delete_cb,
				 &HTMLescape($server),
				 &HTMLescape($username),
				 &HTMLescape($realm),
				) ;

	}
    }


    # If either $cookie_rows or $auth_rows is empty, set appropriate messages.
    $cookie_rows= "<tr><td colspan=6 align=center>&nbsp;<br><b><font face=Verdana size=2>You are not currently sending any cookies through this proxy.</font></b><br>&nbsp;</td></tr>\n"
	if $cookie_rows eq '' ;

    $auth_rows= "<tr><td colspan=4 align=center>&nbsp;<br><b><font face=Verdana size=2>You are not currently authenticated to any sites through this proxy.</font></b><br>&nbsp;</td></tr>\n"
	if $auth_rows eq '' ;


    print <<EOF ;
$HTTP_1_X 200 OK
Cache-Control: no-cache
Pragma: no-cache
Date: $date_header
Content-type: text/html

<html>
<head>
<title>Pengaturan Cookie CGIProxy</title>
</head>
<body style="background-image: url('http://i.imgur.com/zHNCk2e.gif'); background-repeat: repeat; background-position: center; background-attachment: fixed;">
<h3><a href="$return_url">Halaman Awal</a></h3>
<center><h1><font color="white">Daftar Cookies yang Tersimpan :</FONT></h1>

<form action="$action" method=post>
$from_tag

<table bgcolor="#ccffff" border=1>
<tr><th>&nbsp;Hapus Cookie&nbsp;</th>
    <th>&nbsp;Nama Server&nbsp;</th>
    <th>&nbsp;Via&nbsp;</th>
    <th>&nbsp;Nama Cookie&nbsp;</th>
    <th>&nbsp;Nilai&nbsp;</th>
    <th>&nbsp;Keamanan&nbsp;</th>
</tr>
$cookie_rows
</table>

<h3><font color="white">Otentikasi Cookies :</FONT></h3>
<table bgcolor="#ccffcc" border=1>
<tr><th>Hapus Cookie</th>
    <th>&nbsp;Server&nbsp;</th>
    <th>&nbsp;Pengguna&nbsp;</th>
    <th>&nbsp;Bidang&nbsp;</th>
</tr>
$auth_rows
</table>

<p><font color=red>
<input type=submit value="Hapus Cookies">
</font>
</form>

<h3><a href="$clear_cookies_url" color="red">Hapus Seluruh Cookies</a></h3>

<table bgcolor="black" border=5 BORDERCOLORLIGHT=YELLOW BORDERCOLORDARK=BLUE>
<TR> <TD><font color="green">Note : Setelah anda menggunakan CGI Proxy ini, seluruh data penjelajahan anda akan tersimpan secara otomatis. </br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Demi kenyamanan anda dalam menggunakan CGIProxy ini, kami merekomendasikan untuk menghapus seluruh Cookies. 
</BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<B>Terima Kasih</B></font></TR> </TD></table></center>

EOF

    &footer ;
    goto EXIT ;
}
sub get_auth_from_user {
    my($server, $realm, $URL, $tried)= @_ ;
    my($action, $msg) ;
    my($date_header)= &rfc1123_date($now, 0) ;

    $server= &HTMLescape($server) ;
    $realm=  &HTMLescape($realm) ;
    $URL=    &HTMLescape(&wrap_proxy_encode($URL)) ;

    $action= &HTMLescape( $url_start . &wrap_proxy_encode('x-proxy://auth/make_auth_cookie') ) ;

    $msg= "<h3><font color=red>Authorization failed.  Try again.</font></h3>"
	if $tried ;

    print <<EOF ;
$HTTP_1_X 200 OK
Cache-Control: no-cache
Pragma: no-cache
Date: $date_header
Content-type: text/html

<html>
<head><title>Enter username and password for $realm at $server</title></head>
<body>
<h1>Authorization Required</h1>
$msg

<form action="$action" method=post>
<input type=hidden name="s" value="$server">
<input type=hidden name="r" value="$realm">
<input type=hidden name="l" value="$URL">

<table border=1 cellpadding=5>
<tr><th bgcolor="#ff6666">
    Enter username and password for <nobr>$realm</nobr> at $server:</th></tr>
<tr><td bgcolor="#b0b0b0">
    <table cellpadding=0 cellspacing=0>
    <tr><td>Username:</td><td><input name="u" size=20></td></tr>
    <tr><td>Password:</td><td><input type=password name="p" size=20></td>
	<td>&nbsp;&nbsp;&nbsp;<input type=submit value="OK"></tr>
    </table>
</table>
</form>
<p>This requires cookie support turned on in your browser.
<p><i><b>Note:</b> Anytime you use a proxy, you're trusting the owner of that
proxy with all information you enter, including your name and password here.
This is true for <b>any</b> proxy, not just this one.
EOF

    &footer ;
    goto EXIT ;
}



# Alert the user to an unsupported URL, with this intermediate page.
sub unsupported_warning {
    my($URL)= @_ ;
    my($date_header)= &rfc1123_date($now, 0) ;

    &redirect_to($URL) if $QUIETLY_EXIT_PROXY_SESSION ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>WARNING: Entering non-anonymous area!</title></head>
<body>
<h1>WARNING: Entering non-anonymous area!</h1>
<h3>This proxy only supports HTTP and FTP.  Any browsing to another URL will
be directly from your browser, and no longer anonymous.</h3>
<h3>Follow the link below to exit your anonymous browsing session, and
continue to the URL non-anonymously.</h3>
<blockquote><tt><a href="$URL">$URL</a></tt></blockquote>
EOF

    &footer ;
    goto EXIT ;
}


# Alert the user that SSL is not supported, with this intermediate page.
sub no_SSL_warning {
    my($URL)= @_ ;
    my($date_header)= &rfc1123_date($now, 0) ;

    &redirect_to($URL) if $QUIETLY_EXIT_PROXY_SESSION ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>WARNING: SSL not supported, entering non-anonymous area!</title></head>
<body>
<h1>WARNING: SSL not supported, entering non-anonymous area!</h1>
<h3>This proxy as installed does not support SSL, i.e. URLs that start
with "https://".  To support SSL, the proxy administrator needs to install
the Net::SSLeay Perl module, and then this proxy will automatically
support SSL (the
<a href="http://www.jmarshall.com/tools/cgiproxy/">CGIProxy site</a>
has more info).  In the meantime, any browsing to an "https://" URL will
be directly from your browser, and no longer anonymous.</h3>
<h3>Follow the link below to exit your anonymous browsing session, and
continue to the URL non-anonymously.</h3>
<blockquote><tt><a href="$URL">$URL</a></tt></blockquote>
EOF

    &footer ;
    goto EXIT ;
}



# Return "403 Forbidden" message if the target server is forbidden.
sub banned_server_die {
    my($URL)= @_ ;
    my($date_header)= &rfc1123_date($now, 0) ;

    # Here, only quietly redirect out if we get a URL.  This allows calling
    #   routines to force an error, such as when using @BANNED_NETWORKS, or
    #   when a URL is not available.
    &redirect_to($URL) if $QUIETLY_EXIT_PROXY_SESSION && ($URL ne '') ;

    print <<EOF ;
$HTTP_1_X 403 Forbidden
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>The proxy can't access that server, sorry.</title></head>
<body>
<h1>The proxy can't access that server, sorry.</h1>
<p>The owner of this proxy has restricted which servers it can access,
presumably for security or bandwidth reasons.  The server you just tried
to access is not on the list of allowed servers.
EOF

    &footer ;
    goto EXIT ;
}



# Return "403 Forbidden" message if the user's IP address is disallowed.
sub banned_user_die {
    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 403 Forbidden
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>You are not allowed to use this proxy, sorry.</title></head>
<body>
<h1>You are not allowed to use this proxy, sorry.</h1>
<p>The owner of this proxy has restricted which users are allowed to use it.
Based on your IP address, you are not an authorized user.
EOF
    &footer ;
    goto EXIT ;
}

# If so configured, disallow browsing back through this same script.
sub loop_disallowed_die {
    my($URL)= @_ ;
    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 403 Forbidden
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>Proxy cannot loop back through itself</title></head>
<body>
<h1>Proxy cannot loop back through itself</h1>
<p>The URL you tried to access would cause this proxy to access itself,
which is redundant and probably a waste of resources.  The owner of this
proxy has configured it to disallow such looping.
<p>Rather than telling the proxy to access the proxy to access the desired
resource, try telling the proxy to access the resource directly.  The link
below <i>may</i> do this.
<blockquote><tt><a href="$URL">$URL</a></tt></blockquote>
EOF
    &footer ;
    goto EXIT ;
}
# Die if we try to retrieve a secure page while not running on a secure server,
#   because it's a security hole.
sub insecure_die {
    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 200 OK
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>Retrieval of secure URLs through a non-secure proxy is forbidden.</title>
<body>
<h1>Retrieval of secure URLs through a non-secure proxy is forbidden.</h1>
<p>This proxy is running on a non-secure server, which means that retrieval
of pages from secure servers is not permitted.  The danger is that the user
and the end server may believe they have a secure connection between them,
while in fact the link between the user and this proxy is insecure and
eavesdropping may occur.  That's why we have secure servers, after all.
<p>This proxy must run on a secure server before being allowed to retrieve
pages from other secure servers.
EOF

    &footer ;
    goto EXIT ;
}



# Return "403 Forbidden" response for script content-type.
sub script_content_die {
    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 403 Forbidden
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>Script content blocked</title></head>
<body>
<h1>Script content blocked</h1>
<p>The resource you requested (or were redirected to without your knowledge)
is apparently an executable script.  Such resources have been blocked by this
proxy, presumably for your own protection.
<p>Even if you're sure you want the script, you can't get it through this
proxy the way it's configured.  If permitted, try browsing through this proxy
without removing scripts.  Otherwise, you'll need to reconfigure the proxy or
find another way to get the resource.
EOF

    &footer ;
    goto EXIT ;
}



# If images are forbidden, return either a "403 Forbidden" message or a
#   1x1 transparent GIF.
sub non_text_die {
    &return_empty_gif if $RETURN_EMPTY_GIF ;

    my($date_header)= &rfc1123_date($now, 0) ;

    print <<EOF ;
$HTTP_1_X 403 Forbidden
${NO_CACHE_HEADERS}Date: $date_header
Content-type: text/html

<html>
<head><title>Proxy cannot forward non-text files</title></head>
<body>
<h1>Proxy cannot forward non-text files</h1>
<p>Due to bandwidth limitations, the owner of this particular proxy is
forwarding only text files.  For best results, turn off automatic image
loading if your browser lets you.
<p>If you need access to images or other binary data, route your browser
through another proxy (or install one yourself--
<a href="http://www.jmarshall.com/tools/cgiproxy/">it's easy</a>).
EOF
    &footer ;
    goto EXIT ;
}
sub HTMLdie {
    my($msg, $title, $status)= @_ ;
    $title= 'CGIProxy Error' if $title eq '' ;
    $status= '200 OK' if $status eq '' ;
    my($date_header)= &rfc1123_date($now, 0) ;

    # In case this is called early, set $HTTP_1_X to something that works.
    $HTTP_1_X=  $NOT_RUNNING_AS_NPH   ? 'Status:'   : "HTTP/1.0"
	if $HTTP_1_X eq '' ;

    print <<EOF ;
$HTTP_1_X $status
Cache-Control: no-cache
Pragma: no-cache
Date: $date_header
Content-Type: text/html

<html>
<head><title>$title</title></head>
<body>
<h1>$title</h1>
<h3>$msg</h3>
EOF

    &footer ;
    goto EXIT ;
}
sub proxify_js {
    my($in, $top_level)= @_ ;

    # Declaring variables here rather than in blocks below is a little faster.
    my(@out, $element, $token, $last_token, $new_last_token, $newline_since_last_token, $div_ok,
       $term_so_far, $prefix, $sub_expr, $op, $new_val, $cur_val_str, $inc_by,
       $does_write, $in_braces, $in_func, $expr, $next_expr,
       $var_decl, $var, $eq, $value, $skip1, $skip2, $funcname) ;

    # Gmail has a bug-- it uses "new Image().src = ..." where it should use
    #   "(new Image()).src = ..." .  So this is a workaround until they fix it.
    $in=~ s/new Image\(\)\.src =/(new Image()).src =/ ;


    # Note that these patterns contain an embedded set of parentheses that
    #   only match if the input element is a token.
  OUTER:
    while ($div_ok  ? $in=~ /\G($RE_JS_INPUT_ELEMENT_DIV)/gco
		    : $in=~ /\G($RE_JS_INPUT_ELEMENT_REG_EXP)/gco) {

	($element, $token)= ($1, $2) ;

	# If a token was gotten, then set $div_ok according to the token.
	#   Until we get a more complete parser, this is a pretty good guess.
	#   Note that here, "token" also includes DivPunctuator and
	#   RegularExpressionLiteral.
	# DivPunctuator may come after: certain reserved words, identifiers,
	#   the four punctuators ") ] ++ --", numeric and string literals,
	#   and regular expression literals.  To match identifiers but not
	#   the wrong reserved words, it's probably easier to include all
	#   identifiers, then just exclude those reserved words which may
	#   precede RegularExpressionLiteral.  The last line of the pattern
	#   below tests the start of the token for several possible token
	#   types, combined into one pattern.
	# Reserved words that may precede DivPunctuator are qw(this null true false);
	#   reserved words that may precede RegularExpressionLiteral are
	#   qw(case delete do else in instanceof new return throw typeof void).

	if (defined($token)) {
	    $div_ok= $token=~ m#^(?:\)|\]|\+\+|--)$|
				^(?!(?:case|delete|do|else|in|instanceof|new|return|throw|typeof|void)$)
				 (?:\pL|[\$_\\0-9'"]|\.\d|/..)#x ;
	}

	$newline_since_last_token= 1 if $element=~ /^$RE_JS_LINE_TERMINATOR$/o ;
	$new_last_token= '' ;

	# Keep track of whether we're in a function, to correctly handle returns.
	$in_braces++ if $token eq '{' ;
	$in_braces-- if $token eq '}' ;
	$in_func= 0 if $in_braces==0 ;


	# Now, handle cases depending on value of $token.


	# Whitespace and comments may be interspersed within a term.
	if ($token eq '') {
	    if ($term_so_far ne '') {
		$term_so_far.= $element ;
	    } else {
		push(@out, $element) ;
	    }
	} elsif ($token=~ s/^_proxy(\d*)_jslib_/'_proxy'.($1+1).'_jslib_'/e) {
	    $term_so_far.= $token ;
	} elsif ($token=~ /^(?:$RE_JS_NUMERIC_LITERAL|$RE_JS_STRING_LITERAL|$RE_JS_REGULAR_EXPRESSION_LITERAL)$/o) {
	    push(@out, $prefix, $term_so_far) ;
	    $prefix= '' ;
	    $term_so_far= $token ;
	} elsif ($token=~ /^(?:\+\+|--|delete)$/) {
	    # Handle "-->" instead of "--" if needed.
	    if ($token eq '--' and $in=~ /\G\s*>/gco) {
		push(@out, $prefix, $term_so_far, '-->') ;
		$prefix= $term_so_far= '' ;
	    } elsif (($term_so_far ne '') and !$newline_since_last_token) {
		push(@out, $prefix, $term_so_far, $token) ;
		$prefix= $term_so_far= '' ;
	    } else {
		push(@out, $prefix, $term_so_far) ;
		$prefix= $term_so_far= '' ;
		my($o, $p)= &get_next_js_term(\$in) ;
		last unless defined($p) ;
		if ($o ne '') {
		    push(@out, "_proxy_jslib_assign('$token', (" . &proxify_js($o) . "), (" . &proxify_js($p) . "), '')" ) ;
		} else {
		    # Note that $p is guaranteed to be a quoted identifier here.
		    $p=~ s/^'|'$//g ;
		    push(@out, "($p= _proxy_jslib_assign_rval('$token', '$p', '', '', $p))") ;
		}

#		$prefix= $token ;
	    }
	} elsif (($token eq 'eval') && $in=~ /\G($RE_JS_SKIP*\()/gco) {
	    $needs_jslib= 1 ;
	    $term_so_far.= $token . $1 . '_proxy_jslib_proxify_js('
			 . &proxify_js(&get_next_js_expr(\$in,1)) . '))' ;
	    last unless $in=~ /\G\)/gc ;
	    $div_ok= 1 ;

	} elsif ($token=~ /^(?:open|write|writeln|close|replace|load|eval
			       |setInterval|setTimeout|toString
			       |src|href|background|lowsrc|action|location
			       |useMap|longDesc|cite|codeBase|profile
			       |cssText|insertRule|setStringValue|setProperty
			       |backgroundImage|content|cursor|listStyleImage
			       |host|hostname|pathname|port|protocol|search
			       |insertNode|surroundContents|setNamedItem
			       |getElementById|getElementsByTagName
			       |innerHTML|outerHTML|outerText
			       |insertAdjacentHTML|setAttribute|setAttributeNode
			       |appendChild|insertBefore|replaceChild|nodeValue
			       |value|cookie|domain|frames|parent|top|opener
			       |execScript|navigate|showModalDialog|showModelessDialog
			    )$/x) {
	    $needs_jslib= 1 ;
	    $does_write||= ($token eq 'write') || ($token eq 'writeln') || ($token eq 'eval') ;

	    # Handle automatic semicolon insertion.  For more notes about
	    #   automatic semicolon insertion, see comments in
	    #   get_next_js_expr() below.
	    if ($newline_since_last_token
		and $last_token=~ m#^(?:\)|\]|\+\+|--)$|
				    ^(?!(?:case|delete|do|else|in|instanceof|new|typeof|void|function|var)$)
				     (?:\pL|[\$_\\0-9'"]|\.\d|/..)#x )
	    {
		push(@out, $prefix, $term_so_far) ;
		$prefix= $term_so_far= '' ;
	    }

	    $term_so_far=~ s/\.$RE_JS_SKIP*$// ;

	    # Transform to either _proxy_jslib_handle() or _proxy_jslib_assign() call.

	    # First, avoid modifying property names in object literals, which
	    #   are preceded by "{" or "," and followed by ":" .
	    # Not the cleanest here. but should work.
	    if ($last_token=~ /^[{,]$/ and $in=~ /\G($RE_JS_SKIP*:)/gco) {
		push(@out, $prefix, $term_so_far, $token, $1) ;
		$prefix= $term_so_far= '' ;
		$new_last_token= ':' ;
		$div_ok= 0 ;

	    } elsif ($prefix ne '') {
		if ($term_so_far eq '') {
		    push(@out, "$token= _proxy_jslib_assign_rval('$prefix', '$token', '$op', '', $token)") ;
		} else {
		    $term_so_far= "_proxy_jslib_assign('$prefix', $term_so_far, '$token', '', '')" ;
		}
		$prefix= '' ;
		$new_last_token= ')' ;
		$div_ok= 1 ;
	    } elsif ($in=~ /\G$RE_JS_SKIP_NO_LT*(\+\+|--)/gco) {
		$op= $1 ;
		if ($term_so_far eq '') {
		    push(@out, "$token= _proxy_jslib_assign_rval('', '$token', '$op', '', $token)") ;
		} else {
		    $term_so_far= "_proxy_jslib_assign('', $term_so_far, '$token', '$op', '')" ;
		}
		$new_last_token= ')' ;
		$div_ok= 1 ;
	    } elsif ($in=~ /\G$RE_JS_SKIP*(>>>=|<<=|>>=|[+*\/%&|^-]?=(?!=))/gco) {
		$op= $1 ;
		$new_val= &proxify_js(&get_next_js_expr(\$in)) ;
		if ($term_so_far eq '') {
		    push(@out, "$token= _proxy_jslib_assign_rval('', '$token', '$op', ($new_val), $token)") ;
		} else {
		    $term_so_far= "_proxy_jslib_assign('', $term_so_far, '$token', '$op', ($new_val))" ;
		}
		$new_last_token= ')' ;
		$div_ok= 0 ;
	    } else {
		# Pass object and name of property.  Only pass property's value
		#   if object is null, in which case it is needed for return
		#   value.  A little hacky.
		$cur_val_str= ($term_so_far eq '')  ? ", $token"  : '' ;
		$term_so_far= 'null' if $term_so_far eq '' ;
		$term_so_far= "_proxy_jslib_handle($term_so_far, '$token'$cur_val_str)" ;
		$new_last_token= ')' ;
		$div_ok= 1 ;
	    }

	} elsif ($token=~ /^(?:applets|embeds|forms|ids|layers|anchors|images|links)$/) {
	    if ($doing_insert_here and $term_so_far ne '' and $in=~ /\G($RE_JS_SKIP*\[)/gco) {
		$skip1= $1 ;
		$next_expr= &get_next_js_expr(\$in,1) ;
		if ($next_expr=~ /^\s*\d+\s*$/) {
		    $inc_by= $IN_CUSTOM_INSERTION{$token}+$in_mini_start_form{$token} ;
		    $term_so_far.= $token . $skip1 . "$inc_by+(" . &proxify_js($next_expr) . ')]' ;
		} else {
		    $term_so_far.= $token . $skip1 . '(' . &proxify_js($next_expr) . ')]' ;
		}
		last unless $in=~ /\G\]/gc ;
		$new_last_token= ']' ;
	    } else {
		$term_so_far.= $token ;
	    }
	    $div_ok= 1 ;
	} elsif ($token=~ /^(?:if|while|for|with|switch)$/) {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;
	    last unless $in=~ /\G($RE_JS_SKIP*\()/gco ;
	    push(@out, $1, &proxify_js(&get_next_js_expr(\$in,1)), ')') ;
	    last unless $in=~ /\G\)/gc ;
	    $div_ok= 0 ;

	# Parentheses after "catch" and "function" shouldn't be proxified.
	} elsif ($token eq 'catch') {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;
	    last unless $in=~ /\G($RE_JS_SKIP*\()/gco ;
	    push(@out, $1, &get_next_js_expr(\$in,1), ')') ;
	    last unless $in=~ /\G\)/gc ;
	    $div_ok= 0 ;

	# Contrary to the spec, MSIE allows function identifiers to be object
	#   properties in dot notation, so allow "identifier(.identifier)*" .
	} elsif ($token eq 'function') {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;
	    #last unless $in=~ /\G($RE_JS_SKIP*)($RE_JS_IDENTIFIER_NAME)?($RE_JS_SKIP*\()/gco ;   # by the spec
	    last unless $in=~ /\G($RE_JS_SKIP*)($RE_JS_IDENTIFIER_NAME(?:\.(?:$RE_JS_IDENTIFIER_NAME))*)?($RE_JS_SKIP*\()/gco ;
	    ($skip1, $funcname, $skip2)= ($1, $2, $3) ;
	    # Update function name if it's from another proxy's library.
	    $funcname=~ s/^_proxy(\d*)_jslib_/'_proxy'.($1+1).'_jslib_'/e ;
	    push(@out, $skip1, $funcname, $skip2, &get_next_js_expr(\$in,1), ') {') ;
	    last unless $in=~ /\G\)\s*\{/gc ;
	    $in_braces++ ;
	    $in_func= 1 ;
	    $div_ok= 0 ;


	# Handle "var" specially to avoid failing on e.g. "var open= 1 ;" .
	# "var ... in ..." clauses are handled by matching either "=" or "in"
	#   after the identifier name.
	} elsif ($token eq 'var') {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;
	    while (1) {
		$var_decl= &get_next_js_expr(\$in,0) ;
		( ($skip1, $var, $eq, $value)= $var_decl=~ /^($RE_JS_SKIP*)($RE_JS_IDENTIFIER_NAME$RE_JS_SKIP*)(=|in)?(.*)$/s )
		    || last OUTER ;
		# Update variable name if it's from another proxy's library.
		$var=~ s/^_proxy(\d*)_jslib_/'_proxy'.($1+1).'_jslib_'/e ;
		push(@out, $skip1, $var) ;
		push(@out, $eq, &proxify_js($value)) if $eq ne '' ;
		last unless $in=~ /\G,/gc ;
		push(@out, ',') ;
	    }
	    $div_ok= 0 ;


	# Handle "new Function(...)", which contains JS code.
	# This will matter extremely rarely, and is a messy fix, but if not
	#   handled is a privacy hole.
	} elsif ($token eq 'new') {
	    push(@out, $prefix, $term_so_far) ;
	    $prefix= $term_so_far= '' ;
	    if ($in=~ /\G$RE_JS_SKIP*Function\b/gco) {
		push(@out, '_proxy_jslib_new_function') ;
		# teeny bug-- should also push parens if $in is missing them
	    } else {
		push(@out, $token) ;
	    }


	# With every top-level "return", we must _proxy_jslib_flush_write_buffers()
	#   if anything's been written.  Transform "return expr" into
	#   "return ((_proxy_jslib_ret= (expr)), _proxy_jslib_flush_write_buffers(), _proxy_jslib_ret)"
	# Handle other returns simply in next block.
	# jsm-- what about "return foo()"?  $does_write is not set in time
	#   for that case.
	} elsif (($token eq 'return') and !$in_func and $top_level and $does_write) {
	    push(@out, $prefix, $term_so_far) ;
	    $prefix= $term_so_far= '' ;
	    $needs_jslib= 1 ;
	    # Allow commas, but not semicolons; perhaps $allow_multiple in
	    #   get_next_js_expr() should be 3-way.
	    $expr= &get_next_js_expr(\$in,0) ;
	    $expr.= ', ' . &get_next_js_expr(\$in,0) while $in=~ /\G$RE_JS_SKIP*,$RE_JS_SKIP*/gco ;
	    $expr= &proxify_js($expr, 0) ;
	    push(@out,
		 "return ((_proxy_jslib_ret= ($expr)), _proxy_jslib_flush_write_buffers(), _proxy_jslib_ret)") ;
	    $div_ok= 0 ;


	# This is all reserved words except "this", "super", "true", "false",
	#   and "null", which may be part of an object expression.  (Also
	#   missing are the nine reserved words handled directly above.)
	} elsif ($token=~ /^(?:abstract|boolean|break|byte|case|char|class|const|continue|debugger|default|delete|do|else|enum|export|extends|final|finally|float|goto|implements|in|instanceof|int|interface|long|native|package|private|protected|return|short|static|synchronized|throw|throws|transient|try|typeof|void|volatile)$/) {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;


	# This handles identifiers and a certain few reserved words, above.
	# Most reserved words must be handled separately from identifiers, or
	#   else there may be syntatic ambiguities, e.g. "if (foo) (...)".
	} elsif ($token=~ /^$RE_JS_IDENTIFIER_NAME$/o) {
	    # Handle automatic semicolon insertion.  For more notes about
	    #   automatic semicolon insertion, see comments in
	    #   get_next_js_expr() below.
	    if ($newline_since_last_token
		and $last_token=~ m#^(?:\)|\]|\+\+|--)$|
				    ^(?!(?:case|delete|do|else|in|instanceof|new|typeof|void|function|var)$)
				     (?:\pL|[\$_\\0-9'"]|\.\d|/..)#x )
	    {
		push(@out, $prefix, $term_so_far) ;
		$prefix= '' ;
		$term_so_far= $token ;
	    } else {
		$term_so_far.= $token ;
	    }
	} elsif ($token eq '.') {
	    $term_so_far.= '.' ;


	# For "(", get inside parens, proxify, and add to output.
	} elsif ($token eq '(') {
	    $does_write= 1 ;   # any function call could do a write()
	    $term_so_far.= '(' . &proxify_js(&get_next_js_expr(\$in,1)) . ')' ;
	    last unless $in=~ /\G\)/gc ;
	    $new_last_token= ')' ;
	    $div_ok= 1 ;


	# For "[", get inside brackets, proxify, and pass parenthesized as
	#   second parameter to _proxy_jslib_handle().  Or, start new term
	#   if it looks like an array literal instead.
	} elsif ($token eq '[') {
	    # Don't change it for simple integer subscripts.
	    if ($in=~ /\G($RE_JS_SKIP*\d+$RE_JS_SKIP*\])/gco) {
		$term_so_far.= '[' . $1 ;
		$new_last_token= ']' ;
		$div_ok= 1 ;
	    } else {
		$sub_expr= &proxify_js(&get_next_js_expr(\$in,1)) ;
		last unless $in=~ /\G\]/gc ;
		if ($term_so_far ne '') {
		    $needs_jslib= 1 ;
		    $new_last_token= ')' ;
		    if ($prefix ne '') {
			$term_so_far= "_proxy_jslib_assign('$prefix', $term_so_far, ($sub_expr), '', '')" ;
			$prefix= '' ;
			$div_ok= 0 ;
		    } elsif ($in=~ /\G$RE_JS_SKIP_NO_LT*(\+\+|--)/gco) {
			$op= $1 ;
			$term_so_far= "_proxy_jslib_assign('', $term_so_far, ($sub_expr), '$op', '')" ;
			$div_ok= 1 ;
		    } elsif ($in=~ /\G$RE_JS_SKIP*(>>>=|<<=|>>=|[+*\/%&|^-]?=(?!=))/gco) {
			$op= $1 ;
			$new_val= &proxify_js(&get_next_js_expr(\$in)) ;
			$term_so_far= "_proxy_jslib_assign('', $term_so_far, ($sub_expr), '$op', ($new_val))" ;
			$div_ok= 0 ;
		    } else {
			$term_so_far= "_proxy_jslib_handle($term_so_far, ($sub_expr))" ;
			$div_ok= 1 ;
		    }
		} else {
		    $term_so_far= "[$sub_expr]" ;
		    $new_last_token= ']' ;
		    $div_ok= 1 ;
		}
	    }


	# All other punctuators end a term.
	} elsif ($token=~ /^(?:$RE_JS_PUNCTUATOR|$RE_JS_DIV_PUNCTUATOR)$/o) {
	    push(@out, $prefix, $term_so_far, $token) ;
	    $prefix= $term_so_far= '' ;


	} else {
	    &HTMLdie("Shouldn't get here, token= [$token]") ;
	}

	if (defined($token)) {
	    $last_token= $new_last_token ne ''  ? $new_last_token  : $token ;
	    $newline_since_last_token= 0 ;
	}
    }


    push(@out, $prefix, $term_so_far) ;

    # If there's been a write or writeln, then insert a call to flush the
    #   output buffer.  A similar call is inserted into every appropriate
    #   "return" statement; see handling of that above.
    push(@out, " ;\n_proxy_jslib_flush_write_buffers() ;"), $needs_jslib= 1
	if $top_level && $does_write ;

    return join('', @out) ;
}
sub get_next_js_expr {
    my($s, $allow_multiple)= @_ ;
    my(@out, @p, $element, $token, $div_ok, $last_token, $pos) ;

    while (1) {

	# Note that these patterns contain an embedded set of parentheses that
	#   only match if the input element is a token.
	last unless ($div_ok
		     ? $$s=~ /\G($RE_JS_INPUT_ELEMENT_DIV)/gco
		     : $$s=~ /\G($RE_JS_INPUT_ELEMENT_REG_EXP)/gco) ;
	($element, $token)= ($1, $2) ;

	# If $element is either ";" or "," , then end the expression if the
	#   parenthesis stack is empty.  Otherwise, continue.
	if ($element eq ';' or $element eq ',') {
	    pos($$s)-= 1, return join('', @out)  if !$allow_multiple and !@p ;

	
	} elsif ($element=~ /^$RE_JS_LINE_TERMINATOR$/o) {
	    pos($$s)= $pos-length($element), return join('', @out)
		if !$allow_multiple and !@p 
		   and $last_token=~ m#^(?:\)|\]|\+\+|--)$|
				       ^(?!(?:case|delete|do|else|in|instanceof|new|typeof|void|function|var)$)
					(?:\pL|[\$_\\0-9'"]|\.\d|/..)#x
		   and ($pos= pos($$s), $$s=~ /\G$RE_JS_SKIP*$RE_JS_IDENTIFIER_NAME/gco) ;

	# If $element is an opening "parenthesis" (including "?"), then push it
	#   onto the parenthesis stack and continue.
	} elsif ($element=~ /^[(\[{?]$/) {
	    push(@p, $element) ;

	} elsif ($element=~ /^[)\]}:]$/) {
	    pos($$s)-= 1, return join('', @out)  unless @p ;
	    pop(@p)  unless ($element eq ':' and $p[$#p] ne '?') ;

	    # If it's a "}", then return if not $allow_multiple and
	    #   the parenthesis stack is empty.
	    return join('', @out, '}')
		if ($element eq '}' and !@p and !$allow_multiple) ;
	}

	# Whatever we got, add it to the output.
	push(@out, $element) ;

	# If a token was gotten, then set $div_ok according to the token.
	# See the comments in proxify_js() for details.
	if (defined($token)) {
	    $div_ok= $token=~ m#^(?:\)|\]|\+\+|--)$|
				^(?!(?:case|delete|do|else|in|instanceof|new|return|throw|typeof|void)$)
				 (?:\pL|[\$_\\0-9'"]|\.\d|/..)#x ;
	    $last_token= $token ;
	}
    }
    return  @p  ? undef  : join('', @out) ;
}
sub get_next_js_term {
    my($s)= @_ ;
    my($o, $p, $ofrag) ;

    $$s=~ /\G$RE_JS_SKIP*($RE_JS_IDENTIFIER_NAME)/gco or return ;
    $p= "'$1'" ;
    $ofrag= $1 ;

    while ($$s=~ /\G$RE_JS_SKIP*([.\[\(])/gco) {
	$o.= $ofrag ;
	if ($1 eq '.') {
	    $$s=~ /\G$RE_JS_SKIP*($RE_JS_IDENTIFIER_NAME)/gco  or return ;
	    $p= "'$1'" ;
	    $ofrag= '.' . $1 ;
	} elsif ($1 eq '[') {
	    $p= &get_next_js_expr($s, 1) ;
	    $ofrag= "[$p]" ;
	    $$s=~ /\G\]/gco  or return ;
	} elsif ($1 eq '(') {
	    $p= '' ;
	    $ofrag= '(' . &get_next_js_expr($s, 1) . ')' ;
	    $$s=~ /\G\)/gco  or return ;
	}
    }
    return ($o, $p) ;
}
sub js_script_ends_inside_string {
    my($s)= @_ ;
    my($last_line, $token, $div_ok, $pos) ;

    $pos= pos($$s) ;
    ($last_line)= $$s=~ /$RE_JS_LINE_TERMINATOR([^\012\015]*)\z/o ;
    pos($$s)= $pos ;

    # Note that these patterns contain an embedded set of parentheses that
    #   only match if the input element is a token.
    while ($div_ok
	   ? $last_line=~ /\G$RE_JS_INPUT_ELEMENT_DIV/gco
	   : $last_line=~ /\G$RE_JS_INPUT_ELEMENT_REG_EXP/gco)
    {
	$token= $1 ;

	# If a token was gotten, then set $div_ok according to the token.
	# See the comments in proxify_js() for details.
	$div_ok= $token=~ m#^(?:\)|\]|\+\+|--)$|
			    ^(?!(?:case|delete|do|else|in|instanceof|new|return|throw|typeof|void)$)
			     (?:\pL|[\$_\\0-9'"]|\.\d|/..)#x
	    if (defined($token)) ;
    }
    return $last_line=~ /\G['"\/]/  ? 1  : 0 ;
}
sub set_RE_JS {

    # If we decide to support UTF-8, this allows multi-platform compatibility.
    #eval '/\x{2028}/' ;
    #my($utf8_OK)=  $@ eq '' ;


    $RE_JS_WHITE_SPACE= qr/[\x09\x0b\x0c \xa0]|\p{Zs}/ ;
    $RE_JS_LINE_TERMINATOR= qr/[\012\015]/ ;
    $RE_JS_COMMENT= qr#(?>/\*.*?\*/|//[^\012\015]*|<!--[^\012\015]*)#s ;
    $RE_JS_IDENTIFIER_START= qr/\pL|[\$_]|\\u[0-9a-fA-F]{4}/ ;
    $RE_JS_IDENTIFIER_PART=  qr/$RE_JS_IDENTIFIER_START|\p{Mn}|\p{Mc}|\p{Nd}/ ;
    $RE_JS_IDENTIFIER_NAME=  qr/(?>$RE_JS_IDENTIFIER_START$RE_JS_IDENTIFIER_PART*)/ ;


    # Put the longest punctuators first in the list of alternatives.
    $RE_JS_PUNCTUATOR= qr/(?>>>>=?|===|!==|<<=|>>=|[<>=!+*%&|^-]=|\+\+|--|<<|>>|&&|\|\||[{}()[\].;,<>+*%&|^!~?:=-])/ ;
    $RE_JS_DIV_PUNCTUATOR= qr!(?>/=?)! ;


    # Hex literal must come before decimal, so that "0x..." is not parsed as "0"
    #   and a syntax error.  2nd and 3rd alternatives comprise DecimalLiteral.
    $RE_JS_NUMERIC_LITERAL= qr/(?>0[xX][0-9a-fA-F]+|
				  (?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?|
				  \.[0-9]+(?:[eE][+-]?[0-9]+)?)
			       (?!$RE_JS_IDENTIFIER_START)
			      /x ;


    # The last alternative here represents CharacterEscapeSequence, fully expanded.
    $RE_JS_ESCAPE_SEQUENCE= qr/x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|0(?![0-9])|[^0-9xu\012\015]/ ;
    $RE_JS_STRING_LITERAL= qr/"(?>(?:[^"\\\012\015]|\\$RE_JS_ESCAPE_SEQUENCE)*)"|
			      '(?>(?:[^'\\\012\015]|\\$RE_JS_ESCAPE_SEQUENCE)*)'/x ;

    $RE_JS_REGULAR_EXPRESSION_LITERAL= qr!/(?>(?:[^\012\015*\\/]|\\[^\012\015])
					      (?:[^\012\015\\/]|\\[^\012\015])*)
					  /(?>$RE_JS_IDENTIFIER_PART*)
					 !x ;


    # NumericLiteral should come before Punctuator, to avoid parsing e.g.
    #   ".4" as "." and "4".
    $RE_JS_TOKEN= qr/$RE_JS_IDENTIFIER_NAME|$RE_JS_NUMERIC_LITERAL|$RE_JS_PUNCTUATOR|$RE_JS_STRING_LITERAL/ ;
    # Note that Comment has to come before DivPunctuator to correctly parse "//".

    $RE_JS_INPUT_ELEMENT_DIV= qr/(?>$RE_JS_WHITE_SPACE+)|$RE_JS_LINE_TERMINATOR|$RE_JS_COMMENT|
				 ($RE_JS_TOKEN|$RE_JS_DIV_PUNCTUATOR|$RE_JS_REGULAR_EXPRESSION_LITERAL)/x ;

    $RE_JS_INPUT_ELEMENT_REG_EXP= qr/(?>$RE_JS_WHITE_SPACE+)|$RE_JS_LINE_TERMINATOR|$RE_JS_COMMENT|
				     ($RE_JS_TOKEN|$RE_JS_REGULAR_EXPRESSION_LITERAL|$RE_JS_DIV_PUNCTUATOR)/x ;
    $RE_JS_SKIP= qr/(?>$RE_JS_WHITE_SPACE+)|$RE_JS_LINE_TERMINATOR|$RE_JS_COMMENT/ ;
    $RE_JS_SKIP_NO_LT= qr/(?>$RE_JS_WHITE_SPACE+)|$RE_JS_COMMENT/ ;

    # The next two aren't in the spec, but are useful to us below.
    # $RE_JS_BINARY_OPERATOR=  qr/instanceof|in|>>>=?|===|!==|<<=|>>=|[<>=!+*%&|^\/-]=|<<|>>|&&|\|\||[*\/%+<>&^|?:=-]/ ;
    # $RE_JS_UNARY_OPERATOR=   qr/delete|void|typeof|\+\+|--|[+~!-]/ ;

    # $RE_JS_ARRAY_LITERAL= qr/\[,*(?:$RE_JS_ASSIGNMENT_EXPRESSION(?:,+$RE_JS_ASSIGNMENT_EXPRESSION)*)?,*\]/ ;
    # $RE_JS_PROPERTY_NAME= qr/$RE_JS_IDENTIFIER_NAME|$RE_JS_STRING_LITERAL|$RE_JS_NUMERIC_LITERAL/ ;
    # $RE_JS_OBJECT_LITERAL= qr/{(?:$RE_JS_PROPERTY_NAME:$RE_JS_ASSIGNMENT_EXPRESSION
    #                               (?:,$RE_JS_PROPERTY_NAME:$RE_JS_ASSIGNMENT_EXPRESSION)*)?}/x ;

    # $RE_JS_ARGUMENTS= qr/\((?:$RE_JS_ASSIGNMENT_EXPRESSION(?:,$RE_JS_ASSIGNMENT_EXPRESSION)*)?\)/ ;

    # $RE_JS_FUNCTION_EXPRESSION= qr/function$RE_JS_IDENTIFIER_NAME?
    #                                \((?:$RE_JS_IDENTIFIER_NAME(?:,$RE_JS_IDENTIFIER_NAME)*)\)
    #                                {$RE_JS_PROGRAM}
    #                               /x ;


    # $RE_JS_PRIMARY_EXPRESSION= qr/$RE_JS_IDENTIFIER_NAME|$RE_JS_NUMERIC_LITERAL|$RE_JS_STRING_LITERAL|
    #                               $RE_JS_ARRAY_LITERAL|$RE_JS_OBJECT_LITERAL|\($RE_JS_EXPRESSION\)/ ;

    # Here's the approximate simplification.
    # $RE_JS_LEFT_HAND_SIDE_EXPRESSION= qr/(?:new)*
    #                                      (?:$RE_JS_PRIMARY_EXPRESSION|$RE_JS_FUNCTION_EXPRESSION)
    #                                      (?:$RE_JS_ARGUMENTS|\[$RE_JS_EXPRESSION\]|\.$RE_JS_IDENTIFIER_NAME)*
    #                                     /x ;


    # $RE_JS_UNARY_EXPRESSION= qr/$RE_JS_UNARY_OPERATOR*$RE_JS_LEFT_HAND_SIDE_EXPRESSION(?:\+\+|--)?/ ;

    # $RE_JS_ASSIGNMENT_EXPRESSION= qr/$RE_JS_UNARY_EXPRESSION(?:$RE_JS_BINARY_OPERATOR$RE_JS_UNARY_EXPRESSION)*/ ;

    # $RE_JS_EXPRESSION= qr/$RE_JS_ASSIGNMENT_EXPRESSION(?:,$RE_JS_ASSIGNMENT_EXPRESSION)*/ ;


    #-------------------------------------------------------------------------
    # Below are the various patterns that would strictly follow the spec,
    #   collected from above.
    #-------------------------------------------------------------------------

#   $RE_JS_LINE_TERMINATOR= qr/[\012\015]|\x{2028}|\x{2029}/ ;
#   $RE_JS_COMMENT= qr!(?>/\*.*?\*/|//[^\012\015\x{2028}\x{2029}]*)!s ;
#
#   $RE_JS_IDENTIFIER_START= qr/\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}|[\$_]|\\u[0-9a-fA-F]{4}/ ;
#   $RE_JS_IDENTIFIER_PART=  qr/$RE_JS_IDENTIFIER_START|\p{Mn}|\p{Mc}|\p{Nd}|\p{Pc}/ ;
#
#   $RE_JS_ESCAPE_SEQUENCE= qr/x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|0(?![0-9])|[^0-9xu\012\015\x{2028}\x{2029}]/ ;
#   $RE_JS_STRING_LITERAL= qr/"(?>(?:[^"\\\012\015\x{2028}\x{2029}]|\\$RE_JS_ESCAPE_SEQUENCE)*)"|
#			      '(?>(?:[^'\\\012\015\x{2028}\x{2029}]|\\$RE_JS_ESCAPE_SEQUENCE)*)'/x ;
#
#   $RE_JS_REGULAR_EXPRESSION_LITERAL= qr!/(?>(?:[^\012\015\x{2028}\x{2029}*\\/]|\\[^\012\015\x{2028}\x{2029}])
#					      (?:[^\012\015\x{2028}\x{2029}\\/]|\\[^\012\015\x{2028}\x{2029}])*)
#					  /(?>$RE_JS_IDENTIFIER_PART*)
#					 !x ;
#
#   $RE_JS_MEMBER_EXPRESSION= qr/(?:$RE_JS_PRIMARY_EXPRESSION|$RE_JS_FUNCTION_EXPRESSION)
#				   (?:\[$RE_JS_EXPRESSION\]|\.$RE_JS_IDENTIFIER_NAME)*
#				 |new$RE_JS_MEMBER_EXPRESSION$RE_JS_ARGUMENTS
#				/x ;
#   $RE_JS_LEFT_HAND_SIDE_EXPRESSION= qr/(?:new)*$RE_JS_MEMBER_EXPRESSION|
#					 $RE_JS_MEMBER_EXPRESSION$RE_JS_ARGUMENTS
#					 (?:$RE_JS_ARGUMENTS|\[$RE_JS_EXPRESSION\]|\.$RE_JS_IDENTIFIER_NAME)*
#					/x ;


}



#-----------------------------------------------------------------------
#  JavaScript library used when rewriting JavaScript
#-----------------------------------------------------------------------


# These are the functions included in the JavaScript library:
#   Initialization functions:
#     _proxy_jslib_init
#     _proxy_jslib_pass_vars (called from outside)
#   API functions:
#     _proxy_jslib_handle
#     _proxy_jslib_assign
#     _proxy_jslib_assign_rval
#   Secondary functions needed to support above functions:
#     _proxy_jslib_add_to_write_buffer
#     _proxy_jslib_flush_write_buffers
#     _proxy_jslib_new_function
#     _proxy_jslib_doc2win
#     _proxy_jslib_node_is_in_document
#     _proxy_jslib_init_domain
#     _proxy_jslib_full_url
#     _proxy_jslib_full_url_by_frame
#     _proxy_jslib_cookie_to_client
#     _proxy_jslib_cookie_from_client
#     _proxy_jslib_proxify_html
#     _proxy_jslib_proxify_js
#     _proxy_jslib_proxify_comment
#     _proxy_jslib_proxify_script_block
#     _proxy_jslib_proxify_style_block
#     _proxy_jslib_proxify_decl_bang
#     _proxy_jslib_proxify_decl_question
#     _proxy_jslib_proxify_element
#     _proxy_jslib_proxify_attribute
#     _proxy_jslib_proxify_block
#     _proxy_jslib_proxify_css
#     _proxy_jslib_css_full_url
#     _proxy_jslib_return_frame_doc
#     _proxy_jslib_proxy_encode, _proxy_jslib_proxy_decode
#     _proxy_jslib_cookie_encode, _proxy_jslib_cookie_decode
#     _proxy_jslib_wrap_proxy_encode, _proxy_jslib_wrap_proxy_decode
#   Utilities:
#     _proxy_jslib_object_type
#     _proxy_jslib_parse_url
#     _proxy_jslib_parse_full_url
#     _proxy_jslib_pack_flags
#     _proxy_jslib_unpack_flags
#     _proxy_jslib_html_escape
#     _proxy_jslib_html_unescape
#     _proxy_jslib_global_replace
sub return_jslib {
    my($date_header)=    &rfc1123_date($now, 0) ;
    my($expires_header)= &rfc1123_date($now+86400*7, 0) ;  # expires after a week

    # To save time, only set $JSLIB_BODY if it hasn't been set already.
    unless ($JSLIB_BODY) {

	# We must use single-quoted line delimiter ('EOF') to prevent variable
	#   interpolation, etc.  But we also have to pass some constants to it,
	#   so we concatenate a "variable" block and a "fixed" block.  The
	#   "variable" block is constant for each installation, so the library
	#   can still be cached.
	# Note that $ENCODE_DECODE_BLOCK_IN_JS is a user config setting, at top.
	my($script_name_jsq)= $ENV_SCRIPT_NAME ;
	$script_name_jsq=~ s/(["\\])/\\$1/g ;   # make safe for JS quoted string
	my($proxy_group_jsq, @pg, $all_types_js, $mime_type_id_js) ;
	@pg= @PROXY_GROUP ;
	foreach (@pg) { s/(["\\])/\\$1/g }
	$proxy_group_jsq= join(', ', map { "'$_'" } @pg) ;
	$all_types_js=    join(', ', map { "'$_'" } @ALL_TYPES) ;
	$mime_type_id_js= join(', ', map { "'$_':$MIME_TYPE_ID{$_}" } keys %MIME_TYPE_ID) ;

	$JSLIB_BODY= <<EOV . <<'EOF' ;

var _proxy_jslib_SCRIPT_NAME= "$script_name_jsq" ;
var _proxy_jslib_PROXY_GROUP= [$proxy_group_jsq] ;
var _proxy_jslib_ALL_TYPES= [$all_types_js] ;
var _proxy_jslib_MIME_TYPE_ID= {$mime_type_id_js} ;

$ENCODE_DECODE_BLOCK_IN_JS

EOV

var _proxy_jslib_RE_FULL_URL ;
var _proxy_jslib_url_start, _proxy_jslib_url_start_inframe, _proxy_jslib_url_start_noframe,
    _proxy_jslib_is_in_frame, _proxy_jslib_packed_flags, _proxy_jslib_URL ;
var _proxy_jslib_base_url, _proxy_jslib_base_scheme, _proxy_jslib_base_host, _proxy_jslib_base_path, _proxy_jslib_base_file ;
var _proxy_jslib_cookies_are_banned_here, _proxy_jslib_doing_insert_here, _proxy_jslib_SESSION_COOKIES_ONLY,
    _proxy_jslib_COOKIE_PATH_FOLLOWS_SPEC, _proxy_jslib_RESPECT_THREE_DOT_RULE,
    _proxy_jslib_default_script_type, _proxy_jslib_default_style_type ;
var _proxy_jslib_RE, _proxy_jslib_needs_jslib ;
var _proxy_jslib_write_buffers= [ {doc:document, has_js:true} ] ;
var _proxy_jslib_wins= new Array() ;
var _proxy_jslib_ret ;

_proxy_jslib_init() ;


//---- first, the two initialization functions -------------------------

// set _proxy_jslib_URL, _proxy_jslib_url_start, _proxy_jslib_packed_flags,
//   _proxy_jslib_is_in_frame, _proxy_jslib_url_start_inframe, _proxy_jslib_url_start_noframe
// _proxy_jslib_base_url must be set elsewhere, to handle possibly coming from
//   HTTP headers.
function _proxy_jslib_init() {
    // create global regex that matches a full URL, needed for _proxy_jslib_parse_full_url()
    var RE_SCRIPT_NAME= _proxy_jslib_global_replace(_proxy_jslib_SCRIPT_NAME, /(\W)/,
						    function (p) { return '\\'+p[1] } ) ;
    _proxy_jslib_RE_FULL_URL= new RegExp('^('+RE_SCRIPT_NAME+')\\/?([^\\/]*)\\/?(.*)') ;

    // Mozilla sometimes adds 'wyciwyg://' to the URL
    var URL= document.URL.replace(/^wyciwyg:\/\/\d+\//i, '') ;

    var u= _proxy_jslib_parse_full_url(URL) ;
    if (_proxy_jslib_PROXY_GROUP.length) {
	_proxy_jslib_url_start= _proxy_jslib_PROXY_GROUP[Math.floor(Math.random()*_proxy_jslib_PROXY_GROUP.length)]
				+'/'+u[1]+'/' ;
    } else {
	_proxy_jslib_url_start= u[0]+'/'+u[1]+'/' ;
    }
    var flags= _proxy_jslib_unpack_flags(u[1]) ;
    _proxy_jslib_is_in_frame= flags[5] ;
    flags[5]= 1 ;    // that's the frame flag
    _proxy_jslib_url_start_inframe= u[0]+'/'+_proxy_jslib_pack_flags(flags)+'/' ;
    flags[5]= 0 ;
    _proxy_jslib_url_start_noframe= u[0]+'/'+_proxy_jslib_pack_flags(flags)+'/' ;

    _proxy_jslib_packed_flags= u[1] ;
    _proxy_jslib_URL=          u[2] ;

    // this begins life as the hostname
    window._proxy_jslib_document_domain= _proxy_jslib_parse_url(_proxy_jslib_URL)[4] ;
    _proxy_jslib_wins[0]= window ;
//alert('end of init; _p_j_URL=\n['+_proxy_jslib_URL+']') ;
}


// set variables passed in from Perl program.
function _proxy_jslib_pass_vars(base_url, cookies_are_banned_here, doing_insert_here, SESSION_COOKIES_ONLY, COOKIE_PATH_FOLLOWS_SPEC, RESPECT_THREE_DOT_RULE, default_script_type, default_style_type) {
    // set base_ vars from base_url
    _proxy_jslib_base_url= base_url.replace(/^\s+|\s+$/g, '')
				   .replace(/^([\w\+\.\-]+\:\/\/[^\/\?]+)\/?/, "$1/") ;
    _proxy_jslib_base_scheme= _proxy_jslib_base_url.match(/^([\w\+\.\-]+\:)\/\//)[1] ;
    _proxy_jslib_base_host=   _proxy_jslib_base_url.match(/^([\w\+\.\-]+\:\/\/[^\/\?]+)/)[1] ;
    _proxy_jslib_base_path=   _proxy_jslib_base_url.match(/^([^\?]*\/)/)[1] ;
    _proxy_jslib_base_file=   _proxy_jslib_base_url.match(/^([^\?]*)/)[1] ;

    // other settings
    _proxy_jslib_cookies_are_banned_here=  cookies_are_banned_here ;
    _proxy_jslib_doing_insert_here=        doing_insert_here ;
    _proxy_jslib_SESSION_COOKIES_ONLY=     SESSION_COOKIES_ONLY ;
    _proxy_jslib_COOKIE_PATH_FOLLOWS_SPEC= COOKIE_PATH_FOLLOWS_SPEC ;
    _proxy_jslib_RESPECT_THREE_DOT_RULE=   RESPECT_THREE_DOT_RULE ;

    _proxy_jslib_default_script_type=      default_script_type.toLowerCase() ;
    _proxy_jslib_default_style_type=       default_style_type.toLowerCase() ;
}



//---- the general handler routines _proxy_jslib_handle() and _proxy_jslib_assign() ----

// This is used when the property in question IS NOT being assigned to.
function _proxy_jslib_handle (o, property, cur_val) {
//alert('starting handle; o, property, URL=['+o+']['+property+']\n['+_proxy_jslib_URL+']') ;
    // guess when the window object is implied; this only matters with Window's
    //   properties that we handle below
    if ((o===null)  && property.match(/^(location|open|setInterval|setTimeout|frames|parent|top|opener|execScript|navigate|showModalDialog|showModelessDialog|parentWindow)$/) && (window[property]===cur_val)) o= window ;

    // handle eval() specially-- it (oddly) can be a property of any object
    if (property=='eval')
	return function (code) {
		   if (o!=null) return o.eval(_proxy_jslib_proxify_js(code, 0)) ;
		   else         return   eval(_proxy_jslib_proxify_js(code, 0)) ;
	       } ;

    // if object is still null, merely return property value
    if (o==null) return cur_val ;

    var otype= _proxy_jslib_object_type(o) ;

    // handle any non-type-specific properties first, or properties that may
    //   apply to different object types (e.g. the properties of Node)
    switch (property) {
	case 'insertAdjacentHTML':
	    return function (where, text) {
		       if (_proxy_jslib_node_is_in_document(o))
			   return o.insertAdjacentHTML(where, _proxy_jslib_proxify_html(text, false, o.document)[0]) ;
		       return o.insertAdjacentHTML(where, text) ;
		   } ;
	case 'setAttribute':
	    return function (name, value) {
		       if (_proxy_jslib_node_is_in_document(o))
			   return o.setAttribute(name.toLowerCase(), _proxy_jslib_proxify_attribute(name, value)) ;
		       return o.setAttribute(name, value) ;
		   }
	case 'setAttributeNode':
	    return function (newAttr) {
		       if (_proxy_jslib_node_is_in_document(o))
			   newAttr.nodeValue= _proxy_jslib_proxify_attribute(newAttr.nodeName, newAttr.nodeValue) ;
		       return o.setAttributeNode(newAttr) ;
		   }
	case 'appendChild':
	    return function (newChild) {
		       if (_proxy_jslib_node_is_in_document(o) && !_proxy_jslib_node_is_in_document(newChild))
			   return o.appendChild(_proxy_jslib_proxify_element(newChild)) ;
		       return o.appendChild(newChild) ;
		   }
	case 'insertBefore':
	    return function (newChild, refChild) {
		       if (_proxy_jslib_node_is_in_document(o) && !_proxy_jslib_node_is_in_document(newChild))
			   return o.insertBefore(_proxy_jslib_proxify_element(newChild), refChild) ;
		       return o.insertBefore(newChild, refChild) ;
		   }
	case 'replaceChild':
	    return function (newChild, oldChild) {
		       if (_proxy_jslib_node_is_in_document(o) && !_proxy_jslib_node_is_in_document(newChild))
			   return o.replaceChild(_proxy_jslib_proxify_element(newChild), oldChild) ;
		       return o.replaceChild(newChild, oldChild) ;
		   }

	case 'insertRule':
	    return function (rule, index) {
		       return o.insertRule(_proxy_jslib_proxify_css(rule), index) ;
		   }

	// because some sites modify these in place, we must un-proxify these
	//   when retrieving the value.
	// for Location objects, return the object, but handle toString() below
	//   to unproxify it when needed.
	// jsm-- this will still leave Location proxified when toString() is
	//   called implicitly.
	case 'src':
	case 'href':
	case 'background':
	case 'lowsrc':
	case 'action':
	case 'useMap':
	case 'longDesc':
	case 'cite':
	case 'codeBase':
	case 'location':
	    var u= (o!=void 0) ? o[property] : cur_val ;
	    if (u==void 0) return void 0 ;
	    // return unchanged if u is a non-String object
	    if ((typeof u=='object') && (u.toLowerCase==void 0))
		return u ;
	    // if o is a node, then deproxify only if it's in doc or is an Image
	    if ((o.nodeName!==void 0) && (o.nodeType!==void 0) && (o.nodeValue!==void 0)) {
		if (_proxy_jslib_node_is_in_document(o) || (otype=='Image'))
		    return _proxy_jslib_parse_full_url(u)[2] ;
		else return u ;
	    }
//if (u=='') alert('in handle, first switch; typeof, o, property, u, caller=['+typeof(o)+']['+o+']['+property+']['+u+']\n['+arguments.callee.caller+']') ;
	    return _proxy_jslib_parse_full_url(u)[2] ;
    }


    // note use of closures to remember the object o
    switch (otype+'.'+property) {
	// Store new windows in a list so we can insert JS later if needed.
	// Store windows instead of documents, because docs may not be created yet.
	case 'Window.open':
	    return function (url, name, features, replace) {
		       var win= o.open(_proxy_jslib_full_url(url), name, features, replace) ;
		       if (url) win._proxy_jslib_document_domain=
				    _proxy_jslib_parse_url(url)[4] ;
		       _proxy_jslib_wins[_proxy_jslib_wins.length]= win ;
		       return win ;
		   } ;
	case 'Document.open':
	    return function(arg1, name, features, replace) {
		       if (arguments.length<=2) {
			   return o.open(arg1, name) ;
		       } else {
			   // MSIE-specific
			   return o.open(_proxy_jslib_full_url(arg1), name, features, replace) ;
		       }
		   }
	case 'Document.write':
	    // buffer the output by document
	    // no return value
	    return function () {
		       for (var i= 0 ; i<arguments.length ; i++)
//if (confirm('write ['+arguments[i]+']?'))
			   _proxy_jslib_add_to_write_buffer(o, arguments[i]) ;
		   } ;
	case 'Document.writeln':
	    return function () {
		       for (var i= 0 ; i<arguments.length ; i++)
			   _proxy_jslib_add_to_write_buffer(o, arguments[i]) ;
		       _proxy_jslib_add_to_write_buffer(o, '\n') ;
		   } ;
	case 'Document.close':
	    return function() {
//alert('starting Document.close(); # buffers='+_proxy_jslib_write_buffers.length) ;  //  jsm-- all alerts and confirms
		       var buf, i, p ;
		       for (i in _proxy_jslib_write_buffers) {
			   if (_proxy_jslib_write_buffers[i].doc===o) {
			       buf= _proxy_jslib_write_buffers[i] ;
			       if (buf.buf==void 0) break ;
			       p= _proxy_jslib_proxify_html(buf.buf, !buf.has_js, o) ;
			       if (p[2]) return ;   // found frame document
//if (confirm('flushing one buffer;\nhas_js=['+p[1]+']\nout=['+p[0]+']'))
			       o.write(p[0]) ;
			       buf.has_js= buf.has_js || p[1] ;
			       buf.buf= void 0 ;
			       break ;
			   }
		       }
//alert('about to o.close()') ;
		       // jsm-- upon next line, using gmail, Mozilla gives
		       //   "this page contains encrypted/unencrypted mix"
		       //   error.  It would seem to be something that's
		       //   previously written in Document.write() and
		       //   then flushed, but even if we skip the flushing,
		       //   it still happens.  The text that's written is
		       //   correctly proxified, and a Squid log shows there
		       //   is no insecure traffic from the browser.  ???
		       o.close() ;
//alert('ending Document.close()') ;
		   }
	case 'Location.replace':
	    return function (url) {
		       return o.replace(_proxy_jslib_full_url(url)) ;
		   } ;
	case 'Location.toString':
	    return function () {
		       return _proxy_jslib_parse_full_url(o.toString())[2] ;
		   }
	case 'Window.setInterval':
	    return function (codefunc, interval) {
		       if (typeof(codefunc)=='function') {
			   // can't use apply() because of MSIE
			   // return o.setInterval.apply(o, arguments) ;
			   var a= new Array ;
			   for (var i= 0 ; i<arguments.length ; i++) a[i]= 'arguments['+i+']' ;
			   return eval('o.setInterval('+a.join()+')') ;
		       } else {
			   return o.setInterval(_proxy_jslib_proxify_js(codefunc), interval) ;
		       }
		   } ;
	case 'Window.setTimeout':
	    return function (codefunc, delay) {
		       if (typeof(codefunc)=='function') {
			   // can't use apply() because of MSIE
			   // return o.setTimeout.apply(o, arguments) ;
			   var a= new Array ;
			   for (var i= 0 ; i<arguments.length ; i++) a[i]= 'arguments['+i+']' ;
			   return eval('o.setTimeout('+a.join()+')') ;
		       } else {
			   return o.setTimeout(_proxy_jslib_proxify_js(codefunc), delay) ;
		       }
		   } ;
	case 'Document.cookie':
	    return _proxy_jslib_cookie_from_client() ;
	case 'Document.domain':
	    return _proxy_jslib_doc2win(o,_proxy_jslib_wins)._proxy_jslib_document_domain ;
	case 'Window.frames':
	    // jsm-- frames may not have _proxy_jslib_document_domain set, if
	    //   they don't have jslib inserted.  :(
	    var f ;
	    if (o._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o) ;
	    for (f=0 ; f<o.frames.length ; f++) {
		if (o.frames[f]._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o.frames[f]) ;
//if (o.frames[f]._proxy_jslib_document_domain==void 0) alert('o.frames[f]...domain is still undefined') ; // jsm
		if (o.frames[f]._proxy_jslib_document_domain!=o._proxy_jslib_document_domain) {
//		    alert('Tried to access o.frames, but prohibited; domains of o, o.frames[f]=['+o._proxy_jslib_document_domain+']['+o.frames[f]._proxy_jslib_document_domain+']') ;  // jsm-- test a bunch, then remove
		    return null ;
		}
	    }
	    return o.frames ;
	case 'Window.parent':
	    var w= (o.top._proxy_jslib_main_frame===o)  ? o  : o.parent ;
	    if (o._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o) ;
	    if (w._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(w) ;
//alert('in parent; o.domain, w.domain=['+o._proxy_jslib_document_domain+']['+w._proxy_jslib_document_domain+']') ; // jsm-- remove
	    return (w._proxy_jslib_document_domain==o._proxy_jslib_document_domain)  ? w  : null ;
	case 'Window.top':
	    // if window uses frames, translate "top" to "top._proxy_jslib_main_frame".
	    var w= (o.top._proxy_jslib_main_frame!==void 0)  ? o.top._proxy_jslib_main_frame  : o.top ;
	    if (o._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o) ;
	    if (w._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(w) ;
//alert('in Window.top; o, o.name, w, w.name=\n['+o+']['+o.name+']\n['+w+']['+w.name+']') ;  // jsm-- all alerts
//alert('in Window.top; o.domain, w.domain, caller=['+o._proxy_jslib_document_domain+']['+w._proxy_jslib_document_domain+']\n['+arguments.callee.caller+']') ; // jsm-- remove
	    return (w._proxy_jslib_document_domain==o._proxy_jslib_document_domain)  ? w  : null ;
	case 'Window.opener':
	    if (o._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o) ;
	    if (o.opener._proxy_jslib_document_domain==void 0) _proxy_jslib_init_domain(o.opener) ;
	    return (o.opener._proxy_jslib_document_domain==o._proxy_jslib_document_domain)
		?  o.opener  : null ;


	// DOM methods in this block (and above, before this switch/case block)
	case 'Document.getElementById':
	    // Hack-- if element isn't in doc yet but is in output buffer, flush
	    //   buffer and try again.
	    return function (elementId) {
		       var e, i, buf, p ;
		       e= o.getElementById(elementId) ;
		       if (e!=null) return e ;
		       for (i= 0 ; i<_proxy_jslib_write_buffers.length ; i++)
			   if (_proxy_jslib_write_buffers[i].doc===o) break ;
		       if (i==_proxy_jslib_write_buffers.length) return null ;
		       buf= _proxy_jslib_write_buffers[i] ;
		       if (buf.buf==void 0) return null ;
		       if (buf.buf.match(new RegExp('id\\s*=\\s*[\'"]\\s*'+elementId+'\\s*[\'"]', 'i'))) {
			   p= _proxy_jslib_proxify_html(buf.buf, false, o) ;
			   if (p[2]) return ;   // found frame document
			   o.write(p[0]) ;
			   buf.has_js= buf.has_js || p[1] ;
			   buf.buf= void 0 ;
		       }
		       return o.getElementById(elementId) ;
		   }
	case 'Document.getElementsByTagName':
	    return function (tagname) {
		       var i, buf, p ;
		       for (i= 0 ; i<_proxy_jslib_write_buffers.length ; i++)
			   if (_proxy_jslib_write_buffers[i].doc===o) break ;
		       if (i==_proxy_jslib_write_buffers.length) return o.getElementsByTagName(tagname) ;
		       buf= _proxy_jslib_write_buffers[i] ;
		       if (buf.buf==void 0) return o.getElementsByTagName(tagname) ;
		       if (buf.buf.match(new RegExp('<\\s*'+tagname+'\\b', 'i'))) {
			   p= _proxy_jslib_proxify_html(buf.buf, false, o) ;
			   if (p[2]) return ;   // found frame document
			   o.write(p[0]) ;
			   buf.has_js= buf.has_js || p[1] ;
			   buf.buf= void 0 ;
		       }
		       return o.getElementsByTagName(tagname) ;
		   }

	case 'Range.insertNode':
	    return function (newNode) {
		       if (!_proxy_jslib_node_is_in_document(newNode))
			   return o.insertNode(_proxy_jslib_proxify_element(newNode)) ;
		       return o.insertNode(newNode) ;
		   }
	case 'Range.surroundContents':
	    return function (newParent) {
		       if (!_proxy_jslib_node_is_in_document(newParent))
			   return o.surroundContents(_proxy_jslib_proxify_element(newParent)) ;
		       return o.surroundContents(newParent) ;
		   }
	case 'NamedNodeMap.setNamedItem':
	    return function (node) {
		       node.nodeValue= _proxy_jslib_proxify_attribute(node.nodeName, node.nodeValue) ;
		       return o.setNamedItem(node) ;
		   }

	case 'CSSPrimitiveValue.setStringValue':
	    return function (type, value) {
		       if (type==CSSPrimitiveValue.CSS_URI)
			   return o.setStringValue(type, _proxy_jslib_full_url(value)) ;
		       return o.setStringValue(type, value) ;
		   }
	case 'CSSStyleDeclaration.setProperty':
	    return function (name, value, priority) {
		       return o.setProperty(name, _proxy_jslib_proxify_css(value), priority) ;
		   }


	// Netscape-specific in this block
	case 'Layer.load':
	    return function (url, width) {
		       return o.load(_proxy_jslib_full_url(url), width) ;
		   } ;


	// MSIE-specific in this block
	case 'Window.execScript':
	    return function(code, language) {
		       if (language.match(/^\s*(javascript|jscript|ecmascript|livescript|$)/i))
			   return o.execScript(_proxy_jslib_proxify_js(code), language) ;
		       // disallow scripts we don't support
		       return ;
		   }
	case 'Window.navigate':
	    return function (url) {
		       return o.navigate(_proxy_jslib_full_url(url)) ;
		   } ;
	case 'Window.showModalDialog':
	    return function(url, args, features) {
		       return o.showModalDialog(_proxy_jslib_full_url(url), args, features) ;
		   }
	case 'Window.showModelessDialog':
	    return function(url, args, features) {
		       return o.showModelessDialog(_proxy_jslib_full_url(url), args, features) ;
		   }
	// don't need to handle Document.parentWindow, do we?


	// non-standard but supported by both Mozilla and MSIE
	case 'XMLHttpRequest.open':
	    return function(method, url, asyncflag, username, password) {
		       // if absolute URL, verify domain is same as current page
		       if (url.match(/^[\w\+\.\-]*\:/)) {
			   var h1= (_proxy_jslib_parse_url(_proxy_jslib_URL))[4] ;
			   var h2= (_proxy_jslib_parse_url(url))[4] ;
//alert('h1,h2,url=['+h1+']['+h2+']\n['+url+']') ;
			   var d1= (h1.match(/(^|\.)(\w+\.\w+)$/))[2] ;
			   var d2= (h2.match(/(^|\.)(\w+\.\w+)$/))[2] ;
			   if (d1!=d2) return ;   // unallowed domain
		       }
		       // because of how this is used, don't insert the top form
		       return o.open(method, _proxy_jslib_full_url_by_frame(url,1), asyncflag, username, password) ;
		   }


	default:
	    if (typeof(o[property])=='function') {
		return function () {
			   // can't use apply() because of MSIE
			   // return o[property].apply(o, arguments) ;
			   var a= new Array ;
			   for (var i= 0 ; i<arguments.length ; i++) a[i]= 'arguments['+i+']' ;
			   return eval('o[property]('+a.join()+')') ;
		       } ;
	    } else {
		return o[property] ;
	    }

    }
}



// This is used when the property in question IS being assigned to, WITH an object.
function _proxy_jslib_assign (prefix, o, property, op, val) {
    var new_val, otype ;
//alert('starting assign; o, property, URL, old val, new val, caller=\n['+o+']\n['+property+']\n['+_proxy_jslib_URL+']\n['+((o!=void 0)?o[property]:'')+']\n['+val+']\n['+arguments.callee.caller+']') ;

    // guess when the window object is implied
//    if ((o===null)  && (window[property]!==void 0) && (window[property]===cur_val)) o= window ;

    // handle prefix
    if (prefix=='delete') return delete o[property] ;
    if (prefix=='++') {
	val= o[property]+1 ;
	op= '=' ;
    } else if (prefix=='--') {
	val= o[property]-1 ;
	op= '=' ;
    }

// sanity check
if (o==null) alert('in assign, o is null, property, caller=\n['+property+']\n['+arguments.callee.caller+']') ;

    otype= _proxy_jslib_object_type(o) ;

    // Simple variable assignment if o is a Node but not in a document yet.
    // Don't do this with Image objects, because they can load a URL even
    //   when not in a document.  Not very elegant here.
    if ((o.nodeName!==void 0) && (o.nodeType!==void 0) && (o.nodeValue!==void 0) && (otype!='Image')) {
	if (!_proxy_jslib_node_is_in_document(o))
	    return (op=='++' || op=='--')  ? eval('o[property]'+op)  : eval('o[property]'+op+'val') ;
    }


    var opmod= op.match(/=/)  ? op.replace(/=$/, '')  : '' ;

    // For unknown object types, transform common URL properties such as "src".
    //   It's better to proxify a property too much than to open a privacy hole,
    //   which is what happens if such a property is a URL that does not get
    //   proxified.  This also protects against when _proxy_jslib_object_type()
    //   doesn't ID an object correctly.
    // Don't do this if the value it's being assigned to is a non-String object.
    //   This helps when variables have the same name as properties.
    // We don't cover all combinations of properties and operators here; e.g.
    //   URL-like properties are unlikely to use ++ or --, and other
    //   combinations don't usually make sense.  We can revisit if needed.
    switch (property) {
	case 'src':
	case 'href':
	case 'background':
	case 'lowsrc':
	case 'action':
	case 'useMap':
	case 'longDesc':
	case 'cite':
	case 'codeBase':
	case 'location':
	    // test if val is a non-String object
	    if ((typeof val=='object') && (val.toLowerCase==void 0))
		return eval('o[property]'+op+'val') ;
	    if (opmod!='') {
		new_val= _proxy_jslib_parse_full_url(o[property])[2] ;
		eval('new_val' + op + 'val')
	    } else {
		new_val= val ;
	    }
//if (new_val.match(/[01]{6}[A-Z]/)) alert('in assign, about to call full_url; new_val, caller=\n['+new_val+']\n['+arguments.callee.caller+']') ;
	    // return unproxified value
	    o[property]= _proxy_jslib_full_url(new_val) ;
	    return new_val ;

	case 'profile':
	    var u= val.split(/\s+/) ;
	    for (var i= 0 ; i<u.length ; i++)
		u[i]= _proxy_jslib_full_url(u[i]) ;
	    o[property]= u.join(' ') ;
	    return val ;

	case 'cssText':
	    o[property]= _proxy_jslib_proxify_css(val) ;
	    return val ;

	// these are properties of HTMLElement, i.e. could be one of many object types
	case 'innerHTML':
	case 'outerHTML':
	case 'outerText':
	    if (opmod!='') { eval('new_val= o[property]' + opmod + 'val') }
	    else           { new_val= val }
	    o[property]= _proxy_jslib_proxify_html(new_val, false, (o.ownerDocument || o))[0] ;
	    return new_val ;

	// same for properties of Node
	case 'nodeValue':
	    if (opmod!='') { eval('new_val= o[property]' + opmod + 'val') }
	    else           { new_val= val }
	    o[property]= _proxy_jslib_proxify_attribute(property, new_val) ;
	    return new_val ;

	default:
	    var fu, u ;
	    if (otype=='Link' || otype=='Location') {
		fu= _proxy_jslib_parse_full_url(o.href) ;
		u=  _proxy_jslib_parse_url(fu[2]) ;
	    }
	    // u[] has protocol, authentication, host, hostname, port, pathname, search, hash
	    switch (otype+'.'+property) {
		// here we ignore case of "+=", etc.; revisit later if needed
		case 'Link.protocol':
		case 'Location.protocol':
		    val.toLowerCase() ;
		    o.href= _proxy_jslib_full_url(val+'//'+(u[2]!='' ? u[2]+'@' : '')+u[3]+u[6]+u[7]+u[8]) ;
		    return val ;
		case 'Link.host':
		case 'Location.host':
		    val.toLowerCase() ;
		    o.href= _proxy_jslib_full_url(u[1]+'//'+(u[2]!='' ? u[2]+'@' : '')+val+u[6]+u[7]+u[8]) ;
		    return val ;
		case 'Link.hostname':
		case 'Location.hostname':
		    o.href= _proxy_jslib_full_url(u[1]+'//'+(u[2]!='' ? u[2]+'@' : '')+val+(u[5]!='' ? ':'+u[5] : '')+u[6]+u[7]+u[8]) ;
		    return val ;
		case 'Link.port':
		case 'Location.port':
		    o.href= _proxy_jslib_full_url(u[1]+'//'+(u[2]!='' ? u[2]+'@' : '')+u[4]+(val!='' ? ':'+val : '')+u[6]+u[7]+u[8]) ;
		    return val ;
		case 'Link.pathname':
		case 'Location.pathname':
		    o.href= _proxy_jslib_full_url(u[1]+'//'+(u[2]!='' ? u[2]+'@' : '')+u[3]+val+u[7]+u[8]) ;
		    return val ;
		case 'Link.search':
		case 'Location.search':
		    o.href= _proxy_jslib_full_url(u[1]+'//'+(u[2]!='' ? u[2]+'@' : '')+u[3]+u[6]+val+u[8]) ;
		    return val ;
		case 'Document.cookie':
		    return (_proxy_jslib_cookies_are_banned_here
			    ? ''
			    : o.cookie= _proxy_jslib_cookie_to_client(val) ) ;
		case 'Document.domain':
		    var w= _proxy_jslib_doc2win(o,_proxy_jslib_wins) ;
		    // new domain must be suffix of old domain, must contain a
		    //   ".", and must be a complete domain suffix of old value
		    //   (tested here by prefixing with "." before suffix check,
		    //   but allowing if strings are equal).
		    if ( ( (('.'+val)==w._proxy_jslib_document_domain.slice(-val.length-1))
			   || (val==w._proxy_jslib_document_domain) )
			 && val.match(/\./) )
			return (w._proxy_jslib_document_domain= val) ;
//		    else alert('Warning: tried to set document.domain to illegal value: ['+val+'] old value: ['+w._proxy_jslib_document_domain+']') ;  // jsm
		    break ;
		case 'Attr.value':
		    o.value= _proxy_jslib_proxify_attribute(o.name, val) ;
		    return val ;
		case 'CSS2Properties.backgroundImage':
		case 'CSS2Properties.content':
		case 'CSS2Properties.cursor':
		case 'CSS2Properties.listStyleImage':
		    o[property]= _proxy_jslib_proxify_css(val) ;
		    return val ;

		default:
		    return (op=='++' || op=='--')  ? eval('o[property]'+op)  : eval('o[property]'+op+'val') ;
	    }
    }
}



// This is used when the property in question IS being assigned to, WITHOUT an object.
// The value returned is the value to set the variable to.
function _proxy_jslib_assign_rval (prefix, property, op, val, cur_val) {

    // handle prefix
    if (prefix=='delete') return undefined ;  // not quite the same as delete, but close enough
    if (prefix=='++') {
	val= 1 ;
	op= '+=' ;
    } else if (prefix=='--') {
	val=  1 ;
	op= '-=' ;
    }

    if ((typeof val=='object') && (val.toLowerCase==void 0))
	return val ;
    var new_val= cur_val ;
    eval('new_val' + op + 'val') ;

    switch (property) {
	// when there's no object, "location" is the only property that needs proxification
	// jsm-- what about "with" statements?
	case 'location':
	    return _proxy_jslib_full_url(new_val) ;
	default:
	    return new_val ;
    }
}




//---- below are used to support the API functions above ---------------


function _proxy_jslib_add_to_write_buffer(doc, html) {
    var i ;
    for (i= 0 ; i<_proxy_jslib_write_buffers.length ; i++) {
	if (_proxy_jslib_write_buffers[i].doc===doc) break ;
    }
    if (i==_proxy_jslib_write_buffers.length || _proxy_jslib_write_buffers[i].doc!==doc) {
	_proxy_jslib_write_buffers[_proxy_jslib_write_buffers.length]= { doc: doc, buf: html } ;
    } else {
	if (_proxy_jslib_write_buffers[i].buf==void 0) _proxy_jslib_write_buffers[i].buf= '' ;
	_proxy_jslib_write_buffers[i].buf+= html ;
    }
}


// careful-- output of document.write() may be (erroneously?) parsed and
//   executed immediately after document.write() statement.
// Hack here for JS insertions-- if document was created and nothing written on
//   it yet, then insert the JS library if needed.
// Another hack-- since _proxy_jslib_write_buffers may be reset if what's
//   written includes jslib, we exit the loop if that happens.
function _proxy_jslib_flush_write_buffers() {
    var buf, i, p ;

    for (i= 0 ; (_proxy_jslib_write_buffers!=void 0) && (i<_proxy_jslib_write_buffers.length) ; i++) {
	buf= _proxy_jslib_write_buffers[i] ;
	if (buf.buf==void 0) continue ;

	p= _proxy_jslib_proxify_html(buf.buf, !buf.has_js, buf.doc) ;
	if (p[2]) return ;   // found frame document
//var w= _proxy_jslib_doc2win(buf.doc,_proxy_jslib_wins) ; if (w!=void 0) w= w.name ;
//if (confirm('flushing buffer;\nwin=['+w+']\nhas_js=['+p[1]+']\nout=['+p[0]+']'))
	buf.doc.write(p[0]) ;
	buf.has_js= buf.has_js || p[1] ;
	buf.buf= void 0 ;
    }
}



function _proxy_jslib_new_function() {
    arguments[arguments.length-1]= _proxy_jslib_proxify_js(arguments[arguments.length-1]) ;
    // can't use apply() because of MSIE
    // return Function.apply(null, arguments) ;  // Function() same w/ or w/o "new"
    var a= new Array ;
    for (var i= 0 ; i<arguments.length ; i++) a[i]= 'arguments['+i+']' ;
    return eval('Function('+a.join()+')') ;
}


function _proxy_jslib_doc2win(d, wins) {
    var f ;
    for (var i= 0 ; i<wins.length ; i++) {
	if (wins[i].document===d) return wins[i] ;
	if (wins[i].frames.length && (f= _proxy_jslib_doc2win(d, wins[i].frames))) return f ;
    }
    return null ;
}


function _proxy_jslib_node_is_in_document(node) {
    for ( ; (node!=null) && (node.nodeType!=9) ; node= node.parentNode) ;   // Node.DOCUMENT_NODE==9
    return node!=null ;
}



function _proxy_jslib_init_domain(w) {
    // jsm-- not a great solution
    if (w.document.URL=='about:blank') {
	w.setTimeout(function () { _proxy_jslib_init_domain(window) }, 50) ;
	return ;
    }
    var URL= w.document.URL.replace(/^wyciwyg:\/\/\d+\//i, '') ;
    URL= _proxy_jslib_parse_full_url(URL)[2] ;
    URL= decodeURIComponent(URL) ;
    if (URL=='about:blank') {
	w.setTimeout(function () { _proxy_jslib_init_domain(window) }, 200) ;
	return ;
    }
    w._proxy_jslib_document_domain= _proxy_jslib_parse_url(URL)[4] ;
}



function _proxy_jslib_full_url(uri_ref) {
    if (uri_ref==null) return '' ;
//if (uri_ref.match(/\/[01]{6}[A-Z]\//)) alert('in full_url, uri_ref, caller=\n['+uri_ref+']\n['+arguments.callee.caller+']') ;
//if (uri_ref.match(/_proxy_jslib_flush_write_buffers/)) alert('in full_url, uri_ref, caller=\n['+uri_ref+']\n['+arguments.callee.caller+']') ; 

    uri_ref= uri_ref.replace(/^\s+|\s+$/g, '') ;
    if (/^x\-proxy\:\/\//i.test(uri_ref))  return '' ;

    if (/^(javascript|livescript)\:/i.test(uri_ref)) {
	uri_ref= uri_ref.replace(/^(javascript|livescript)\:/i, '') ;
	return 'javascript:' + _proxy_jslib_proxify_js(uri_ref, 1) ;
    }

    var uf= uri_ref.match(/^([^\#]*)(\#.*)?/) ;
    var uri= uf[1] ;
    var frag=  uf[2]  ? uf[2]  : '' ;
    if (uri=='')  return uri_ref ;

    uri= uri.replace(/[\r\n]/g, '') ;

    var absurl ;
    if      (/^[\w\+\.\-]*\:/.test(uri))  { absurl= uri               }
    else if (/^\/\//.test(uri))           { absurl= _proxy_jslib_base_scheme + uri }
    else if (/^\//.test(uri))             { absurl= _proxy_jslib_base_host   + uri }
    else if (/^\?/.test(uri))             { absurl= _proxy_jslib_base_file   + uri }
    else                                  { absurl= _proxy_jslib_base_path   + uri }

    return _proxy_jslib_url_start + _proxy_jslib_wrap_proxy_encode(absurl) + frag ;
}


function _proxy_jslib_full_url_by_frame(uri_ref, is_frame) {
    var old_url_start= _proxy_jslib_url_start ;
    _proxy_jslib_url_start= is_frame  ? _proxy_jslib_url_start_inframe  : _proxy_jslib_url_start_noframe ;
    var ret= _proxy_jslib_full_url(uri_ref) ;
    _proxy_jslib_url_start= old_url_start ;
    return ret ;
}


function _proxy_jslib_wrap_proxy_encode(URL) {
    var uf= URL.match(/^([^\#]*)(\#.*)?/) ;
    var uri= uf[1] ;
    var frag=  uf[2]  ? uf[2]  : '' ;

    uri= _proxy_jslib_proxy_encode(uri) ;
    uri= uri.replace(/\=/g, '=3d').replace(/\?/g, '=3f').replace(/\#/g, '=23').replace(/\%/g, '=25') ;

    return uri + frag ;
}

function _proxy_jslib_wrap_proxy_decode(enc_URL) {
    var uf= enc_URL.match(/^([^\#]*)(\#.*)?/) ;
    var uri= uf[1] ;
    var frag=  uf[2]  ? uf[2]  : '' ;

    uri= uri.replace(/\=(..)/g, function (s,p1) { return String.fromCharCode(eval('0x'+p1)) } ) ;
    uri= _proxy_jslib_proxy_decode(uri) ;

    return uri + frag ;
}



function _proxy_jslib_cookie_to_client(cookie) {
    if (_proxy_jslib_cookies_are_banned_here) return '' ;

    var u= _proxy_jslib_parse_url(_proxy_jslib_URL) ;
    if (u==null) {
	alert("CGIProxy Error: Can't parse URL <"+_proxy_jslib_URL+">; not setting cookie.") ;
	return '' ;
    }
    var source_server= u[4] ;
    var source_path= u[6] ;
    if (source_path.substr(0,1)!='/') source_path= '/' + source_path ;

    var name, value, expires_clause, path, domain, secure_clause ;
    var new_name, new_value, new_cookie ;

    name= value= expires_clause= path= domain= secure_clause=
	new_name= new_value= new_cookie= '' ;

    if (/^\s*([^\=\;\,\s]*)\s*\=?\s*([^\;\,\s]*)/.test(cookie)) {
	name= RegExp.$1 ; value= RegExp.$2 ;
    }
    if (/\;\s*(expires\s*\=[^\;]*)/i.test(cookie))        expires_clause= RegExp.$1 ;
    if (/\;\s*path\s*\=\s*([^\;\,\s]*)/i.test(cookie))    path= RegExp.$1 ;
    if (/\;\s*domain\s*\=\s*([^\;\,\s]*)/i.test(cookie))  domain= RegExp.$1 ;
    if (/\;\s*(secure\b)/i.test(cookie))                  secure_clause= RegExp.$1 ;

    if (path=='') path= _proxy_jslib_COOKIE_PATH_FOLLOWS_SPEC  ? source_path  : '/' ;

    if (domain=='') {
	domain= source_server ;
    } else {
	domain= domain.replace(/\.+$/, '') ;
	domain= domain.replace(/\.{2,}/g, '.') ;
	if ( (source_server.substr(source_server.length-domain.length)!=domain.toLowerCase()) && ('.'+source_server!=domain) )
	    return '' ;
	var dots= domain.match(/\./g) ;
	if (_proxy_jslib_RESPECT_THREE_DOT_RULE) {
	    if (dots.length<3 && !( dots.length>=2 && /\.(com|edu|net|org|gov|mil|int)$/i.test(domain) ) )
		return '' ;
	} else {
	    if (dots.length<2) {
		if (domain.match(/^\./)) return '' ;
		domain= '.'+domain ;
		if (dots.length<1) return '' ;
	    }
	}
    }

    new_name=  _proxy_jslib_cookie_encode('COOKIE;'+name+';'+path+';'+domain) ;
    new_value= _proxy_jslib_cookie_encode(value+';'+secure_clause) ;

    if (_proxy_jslib_SESSION_COOKIES_ONLY && (expires_clause!='')) {
	/^expires\s*\=\s*(.*)$/i.test(expires_clause) ;
	var expires_date= RegExp.$1.replace(/\-/g, ' ') ;  // Date.parse() can't handle "-"
	if ( Date.parse(expires_date) > (new Date()).getTime() ) expires_clause= '' ;
    }

    new_cookie= new_name+'='+new_value ;
    if (expires_clause!='') new_cookie= new_cookie+'; '+expires_clause ;
    new_cookie= new_cookie+'; path='+_proxy_jslib_SCRIPT_NAME+'/' ;
    if (secure_clause!='')  new_cookie= new_cookie+'; '+secure_clause ;

    return new_cookie ;
}


function _proxy_jslib_cookie_from_client() {
    if (_proxy_jslib_cookies_are_banned_here) return '' ;
    if (!document.cookie) return '' ;

    var target_path, target_server, target_scheme ;
    var u= _proxy_jslib_parse_url(_proxy_jslib_URL) ;
    if (u==null) {
	alert("CGIProxy Error: Can't parse URL <"+_proxy_jslib_URL+">; not using cookie.") ;
	return ;
    }
    target_scheme= u[1] ;
    target_server= u[4] ;
    target_path= u[6] ;
    if (target_path.substr(0,1)!='/') target_path= '/' + target_path ;

    var matches= new Array() ;
    var pathlen= new Object() ;
    var cookies= document.cookie.split(/\s*;\s*/) ;
    for (var c in cookies) {
	var nv= cookies[c].split(/=/, 2) ;
	var name=  _proxy_jslib_cookie_decode(nv[0]) ;
	var value= _proxy_jslib_cookie_decode(nv[1]) ;
	var n= name.split(/;/) ;
	if (n[0]=='COOKIE') {
	    var cname, path, domain, cvalue, secure ;
	    cname= n[1] ; path= n[2] ; domain= n[3].toLowerCase() ;
	    var v= value.split(/;/) ;
	    cvalue= v[0] ; secure= v[1] ;
	    if (secure!='' && secure!=null && target_scheme!='https:') continue ;
	    if ( ((target_server.substr(target_server.length-domain.length)==domain)
		  || (domain=='.'+target_server))
		&& target_path.substr(0, path.length)==path )
	    {
		matches[matches.length]= cname  ? cname+'='+cvalue  : cvalue ;
		pathlen[cname+'='+cvalue]= path.length ;
	    }
	}
    }

    matches.sort(function (v1,v2) { return (pathlen[v2]-pathlen[v1]) } ) ;

    return matches.join('; ') ;
}




// returns [new_html, needs_jslib, found_frameset]
function _proxy_jslib_proxify_html(html, is_full_page, doc) {
    var out= '' ;
    var m1, m2, m3, rem ;
    var html_pos, head_pos ;

    if (is_full_page) _proxy_jslib_needs_jslib= false ;

    while (html!='') {
	var start= html.match(/^[^\<]*/) ;
	out+= start[0] ;
	html= html.substr(start[0].length) ;

	// because not all browsers support non-greedy matching, next six
	//   blocks emulate it.
	if (m1= html.match(/^\<\!\-\-/)) {
	    rem= html.substr(4) ;
	    if (rem.match(/\-\-\>/)) {
		m2= rem.match(/\-\-\s*\>/) ;
	    } else if (m2= rem.match(/\>/)) {
	    } else {
		html= '' ;
		break ;
	    }
	    var comment= html.substr(0, 4+m2.index+m2[0].length) ;
	    html= html.substr(comment.length) ;
	    out+= _proxy_jslib_proxify_comment(comment) ;
	    continue ;
	}

	if (m1= html.match(/^\<\s*script\b/i)) {
	    rem= html.substr(m1[0].length) ;
	    if (m2= rem.match(/\<\s*\/script\b/i)) {
		rem= html.substr(m1[0].length+m2.index+m2[0].length) ;
		if (m3= rem.match(/\>/)) {
		    var script_block= html.substr(0, m1[0].length+m2.index+m2[0].length+m3.index+1) ;
		    html= html.substr(script_block.length) ;
		    out+= _proxy_jslib_proxify_script_block(script_block) ;
		    continue ;
		}
	    }
	}

	if (m1= html.match(/^\<\s*style\b/i)) {
	    rem= html.substr(m1[0].length) ;
	    if (m2= rem.match(/\<\s*\/style\b/i)) {
		rem= html.substr(m1[0].length+m2.index+m2[0].length) ;
		if (m3= rem.match(/\>/)) {
		    var style_block= html.substr(0, m1[0].length+m2.index+m2[0].length+m3.index+1) ;
		    html= html.substr(style_block.length) ;
		    out+= _proxy_jslib_proxify_style_block(style_block) ;
		    continue ;
		}
	    }
	}

	if (m1= html.match(/^\<\![^\>]*\>?/)) {
	    out+= _proxy_jslib_proxify_decl_bang(m1[0]) ;
	    html= html.substr(m1[0].length) ;
	    continue ;
	}

	if (m1= html.match(/^\<\?[^\>]*\>?/)) {
	    out+= _proxy_jslib_proxify_decl_question(m1[0]) ;
	    html= html.substr(m1[0].length) ;
	    continue ;
	}

	// gmail has a bug where a <body> tag is missing the closing ">",
	//   so disallow "<" in tag here to avoid including following tag.
	//if (m1= html.match(/^\<[^\>]*\>?/)) {
	if (m1= html.match(/^\<[^\<>]*\>?/)) {
//if (m1[0].match(/[01]{6}[A-Z]/)) alert('in proxify_html; m1[0], caller=\n['+m1[0]+']\n'+arguments.callee.caller+']') ;
	    // For JS insertions, and <frameset> tags.  Ignore declarations for now.
	    if (m2= m1[0].match(/^\<\s*([A-Za-z][\w\.\:\-]*)/)) {
		var tag_name= m2[1].toLowerCase() ;
		if ((tag_name=='frameset') && _proxy_jslib_doing_insert_here && !_proxy_jslib_is_in_frame) {
		    _proxy_jslib_return_frame_doc(_proxy_jslib_wrap_proxy_encode(_proxy_jslib_URL), doc) ;
		    return ['', false, true] ;
		}
		if      (tag_name=='html') { html_pos= out.length }
		else if (tag_name=='head') { head_pos= out.length }
	    }

	    out+= _proxy_jslib_proxify_element(m1[0]) ;
	    html= html.substr(m1[0].length) ;

	    continue ;
	}

	break ;
    }

    // This handles rare case.  Don't worry about top insertion.  Hacky.
    if (is_full_page && _proxy_jslib_needs_jslib) {

	var jslib_block= '<script type="text/javascript" src="'
		       + _proxy_jslib_html_escape(_proxy_jslib_url_start+_proxy_jslib_wrap_proxy_encode('x-proxy://scripts/jslib'))
		       + '"><\/script>\n' ;
	var base_url_jsq= _proxy_jslib_global_replace(_proxy_jslib_base_url, /(["\\])/,
						      function (p) { return '\\'+p[1] } ) ;
	var insert_string= '<script type="text/javascript">_proxy_jslib_pass_vars("'
			 + base_url_jsq + '",'
			 + _proxy_jslib_cookies_are_banned_here + ','
			 + _proxy_jslib_doing_insert_here + ','
			 + _proxy_jslib_SESSION_COOKIES_ONLY + ','
			 + _proxy_jslib_COOKIE_PATH_FOLLOWS_SPEC + ','
			 + _proxy_jslib_RESPECT_THREE_DOT_RULE + ',"'
			 + _proxy_jslib_default_script_type + '","'
			 + _proxy_jslib_default_style_type + '");<\/script>\n' ;
	var insert_pos= head_pos || html_pos || 0 ;
	out= out.substring(0,insert_pos) + jslib_block + insert_string + out.substring(insert_pos) ;
    }

    return [out, _proxy_jslib_needs_jslib] ;
}



// For now, remove script-written comments and other SGML declarations.
function _proxy_jslib_proxify_comment(comment) {
    return '' ;
}

function _proxy_jslib_proxify_decl_bang(decl_bang) {
    return '' ;
}

function _proxy_jslib_proxify_decl_question(decl_question) {
    return '' ;
}


function _proxy_jslib_proxify_script_block(script_block) {
    var m1, m2, tag, script, attrs, attr, name ;
    attr= new Object() ;

    m1= script_block.match(/^(\<\s*script\b[^\>]*\>)((.|\s)*)\<\s*\/script\b[^\>]*\>$/i) ;
    var o_n_j= _proxy_jslib_needs_jslib ;   // hack hack
    tag= _proxy_jslib_proxify_html(m1[1])[0] ;
    _proxy_jslib_needs_jslib= o_n_j ;
    script= m1[2] ;
    attrs= tag.match(/^\<\s*script\b([^\>]*)\>/i)[1] ;

    while (m2= attrs.match(/([A-Za-z][\w\.\:\-]*)\s*(\=\s*(\"([^\"\>]*)\"?|\'([^\'\>]*)\'?|([^\'\"][^\s\>]*)))?/)) {
	attrs= attrs.substr(m2[0].length) ;
	name= m2[1].toLowerCase() ;
	if (attr[name]!=null) continue ;
	attr[name]= m2[4]  ? m2[4]  : m2[5]  ? m2[5]  : m2[6]  ? m2[6]  : '' ;
	attr[name]= _proxy_jslib_html_unescape(attr[name]) ;
    }
    if (attr.type!=null) attr.type= attr.type.toLowerCase() ;
    if (!attr.type && attr.language) {
	attr.type= attr.language.match(/javascript|ecmascript|livescript|jscript/i)
						     ? 'application/x-javascript'
		 : attr.language.match(/css/i)       ? 'text/css'
		 : attr.language.match(/vbscript/i)  ? 'application/x-vbscript'
		 : attr.language.match(/perl/i)      ? 'application/x-perlscript'
		 : attr.language.match(/tcl/i)       ? 'text/tcl'
		 : '' ;
    }
    if (!attr.type) attr.type= _proxy_jslib_default_script_type ;

    // For now, don't worry about "</script" inside JS-written scripts.

    script= _proxy_jslib_proxify_block(script, attr.type, 1) ;

    return tag+script+'<\/script>' ;
}


function _proxy_jslib_proxify_style_block(style_block) {
    var m1, m2, tag, stylesheet, attrs, type ;
    m1= style_block.match(/^(\<\s*style\b[^\>]*\>)((.|\s)*)\<\s*\/style\b[^\>]*\>$/i) ;
    var o_n_j= _proxy_jslib_needs_jslib ;   // hack hack
    tag= _proxy_jslib_proxify_html(m1[1])[0] ;
    _proxy_jslib_needs_jslib= o_n_j ;
    stylesheet= m1[2] ;
    attrs= tag.match(/^\<\s*style\b([^\>]*)\>/i)[1] ;

    while (m2= attrs.match(/([A-Za-z][\w\.\:\-]*)\s*(\=\s*(\"([^\"\>]*)\"?|\'([^\'\>]*)\'?|([^\'\"][^\s\>]*)))?/)) {
	attrs= attrs.substr(m2[0].length) ;
	if (m2[1].toLowerCase()=='type') {
	    type= m2[4]!=null  ? m2[4]  : m2[5]!=null  ? m2[5]  : m2[6]!=null  ? m2[6]  : '' ;
	    type= _proxy_jslib_html_unescape(type).toLowerCase() ;
	    break ;
	}
    }
    if (!type) type= _proxy_jslib_default_style_type ;
    stylesheet= _proxy_jslib_proxify_block(stylesheet, type, 1) ;

    return tag+stylesheet+'<\/style>' ;
}



// used for tags, element nodes (nodes+children are modified in place), and
//   DocumentFragment nodes.
function _proxy_jslib_proxify_element(element) {
    // Unfortunately, attr{} may have extra properties if a Web page changes
    //   anything in the Object prototype.  Thus, we use names[] to keep track
    //   of the tag's attributes.  We do this elsewhere too.
    var m1, m2, tag_name, attrs, attr, names, name, i, rebuild, end_slash,
	old_url_start, flags ;
    attr= new Object() ;
    names= new Array() ;

    // handle either tag strings or Node objects
    if (typeof element=='string') {
	if (!(m1= element.match(/^\<\s*([A-Za-z][\w\.\:\-]*)\s*([^\>]*)/))) return element ;
	tag_name= m1[1].toLowerCase() ;
	attrs= m1[2] ;
	// ignore possibility of <frameset> tag
	if (attrs=='') return element ;

	while (m2= attrs.match(/([A-Za-z][\w\.\:\-]*)\s*(\=\s*(\"([^\"\>]*)\"?|\'([^\'\>]*)\'?|([^\'\"][^\s\>]*)))?/)) {
	    attrs= attrs.substr(m2.index+m2[0].length) ;
	    name= m2[1].toLowerCase() ;
	    if (attr[name]!=null) { rebuild= 1 ; continue }
	    attr[name]= (m2[4]!='' && m2[4]!=void 0) ? m2[4]
		      : (m2[5]!='' && m2[5]!=void 0) ? m2[5]
		      : (m2[6]!='' && m2[6]!=void 0) ? m2[6]
		      : '' ;
	    attr[name]= _proxy_jslib_html_unescape(attr[name]) ;
	    names[names.length]= name ;
	}

    } else if (typeof element=='object') {
	// don't proxify Image objects, since they're already proxified
	if (_proxy_jslib_object_type(element)=='Image') return element ;
	
	if (element.nodeType==1) {           // Node.ELEMENT_NODE
	    tag_name= element.nodeName.toLowerCase() ;
	    for (i= 0 ; i<element.attributes.length ; i++) {
		name= element.attributes[i].nodeName.toLowerCase() ;
		attr[name]= element.attributes[i].nodeValue ;
		names[names.length]= name ;
	    }
	} else if (element.nodeType==11) {   // Node.DOCUMENT_FRAGMENT_NODE
	    for (i= 0 ; i<element.childNodes.length ; i++)
		_proxy_jslib_proxify_element(element.childNodes[i]) ;
	    return element ;
	} else if (element.nodeType==3) {     // Node.TEXT_NODE
	    return element ;
	} else {
	    alert('Error: unhandled node type==['+element.nodeType+']\ncaller=[\n'+arguments.callee.caller+']') ;
	    return ;
	}
    } else {
	alert('Error: unhandled typeof==['+(typeof element)+ ']') ;
	return ;
    }


    // Now we have tag_name, attr[], and names[] set.

//    for (name in attr) {
    for (i= 0 ; i<names.length ; i++) {
	name= names[i] ;
	// for now, simply delete attributes with script macros
	if (attr[name].match(/\&\{.*\}\;/)) { delete attr[name] ; rebuild= 1 ; continue }

	if (name.match(/^on/)) {
	    attr[name]= _proxy_jslib_proxify_block(attr[name], _proxy_jslib_default_script_type, 1) ;
	    rebuild= 1 ;
	}
    }

    if (attr.style!=null) {
	if (attr.style.match(/(expression|function)\s*\(/i ))
	    attr.style= _proxy_jslib_global_replace(attr.style, /\b((expression|function)\s*\()([^\)]*)/i,
						    function (p) { return p[1]+_proxy_jslib_proxify_js(p[3]) } ) ;

	attr.style= _proxy_jslib_proxify_block(attr.style, _proxy_jslib_default_style_type, 1) ;
	rebuild= 1 ;
    }

    // huge simplification of tag-specific block
    if (attr.href!==void 0)        { attr.href=        _proxy_jslib_full_url(attr.href) ;        rebuild= 1 }
    if (attr.src!==void 0)         {
	if (tag_name=='frame' || tag_name=='iframe') {
				     attr.src=         _proxy_jslib_full_url_by_frame(attr.src,1) ; rebuild= 1 ;
	} else if (tag_name=='script') {   // messy  :P
	    var old_url_start= _proxy_jslib_url_start ;
	    var flags= _proxy_jslib_unpack_flags(_proxy_jslib_packed_flags) ;
	    flags[6]= (attr.type!==void 0)  ? attr.type  : _proxy_jslib_default_script_type ;
	    _proxy_jslib_url_start= _proxy_jslib_SCRIPT_NAME + '/' + _proxy_jslib_pack_flags(flags) + '/' ;
				     attr.src=         _proxy_jslib_full_url(attr.src) ;         rebuild= 1 ;
	    _proxy_jslib_needs_jslib= true ;
	    _proxy_jslib_url_start= old_url_start ;
	} else                     { attr.src=         _proxy_jslib_full_url(attr.src) ;         rebuild= 1 }
    }
    if (attr.lowsrc!==void 0)      { attr.lowsrc=      _proxy_jslib_full_url(attr.lowsrc) ;      rebuild= 1 }
    if (attr.dynsrc!==void 0)      { attr.dynsrc=      _proxy_jslib_full_url(attr.dynsrc) ;      rebuild= 1 }
    if (attr.action!==void 0)      { attr.action=      _proxy_jslib_full_url(attr.action) ;      rebuild= 1 }
    if (attr.background!==void 0)  { attr.background=  _proxy_jslib_full_url(attr.background) ;  rebuild= 1 }
    if (attr.usemap!==void 0)      { attr.usemap=      _proxy_jslib_full_url(attr.usemap) ;      rebuild= 1 }
    if (attr.cite!==void 0)        { attr.cite=        _proxy_jslib_full_url(attr.cite) ;        rebuild= 1 }
    if (attr.longdesc!==void 0)    { attr.longdesc=    _proxy_jslib_full_url(attr.longdesc) ;    rebuild= 1 }
    if (attr.codebase!==void 0)    { attr.codebase=    _proxy_jslib_full_url(attr.codebase) ;    rebuild= 1 }
    if (attr.pluginspage!==void 0) { attr.pluginspage= _proxy_jslib_full_url(attr.pluginspage) ; rebuild= 1 }

    if ((tag_name=='meta') && attr['http-equiv'].match(/^\s*refresh\b/i)) {
	attr.content= _proxy_jslib_global_replace(
			  attr.content,
			  /(\;\s*URL\=)\s*(\S*)/i,
			  function (a) { return a[1] + _proxy_jslib_full_url(a[2]) } ) ;
	rebuild= 1 ;
    }

    // Now attr[] has been modified correctly.


    if (typeof element == 'object') {
	if (rebuild)
	    for (i= 0 ; i<element.attributes.length ; i++)
		element.attributes[i].nodeValue= attr[element.attributes[i].nodeName.toLowerCase()] ;
	for (i= 0 ; i<element.childNodes.length ; i++) {
	    if (element.childNodes[i].nodeType==1) {
		_proxy_jslib_proxify_element(element.childNodes[i]) ;
	    }
	}
	return element ;
    }

    if (!rebuild) return element ;

    attrs= '' ;
//    for (name in attr) {
    for (i= 0 ; i<names.length ; i++) {
	name= names[i] ;
	if (attr[name]==null) continue ;
	if (attr[name]=='')  { attrs+= ' '+name ; continue }
	if (!attr[name].match(/\"/) || attr[name].match(/\'/)) {
	    attrs+= ' '+name+'="'+_proxy_jslib_html_escape(attr[name])+'"' ;
	} else {
	    attrs+= ' '+name+"='"+_proxy_jslib_html_escape(attr[name])+"'" ;
	}
    }

    end_slash= element.match(/\/\s*>?$/)  ? ' /'  : '' ;
    return '<'+tag_name+attrs+end_slash+'>' ;
}


function _proxy_jslib_element2tag (e) {
    var ret= '', i ;
if (e.nodeType!=1) alert('in element2tag; nodeType=['+e.nodeType+']') ;
    for (i= 0 ; i<e.attributes.length ; i++)
	ret+= ' '+e.attributes[i].nodeName+'="'+e.attributes[i].nodeValue+'"' ;
    ret= '<'+e.tagName+ret+'>' ;
    for (i=0 ; i<e.childNodes.length ; i++)
	if      (e.childNodes[i].nodeType==1) ret+= '\n'+_proxy_jslib_element2tag(e.childNodes[i]) ;
	else if (e.childNodes[i].nodeType==3) ret+= '\n'+e.childNodes[i].nodeValue ;
    return ret ;
}



// This mimics much of _proxy_jslib_proxify_element(), above.
function _proxy_jslib_proxify_attribute(name, value) {
    if (/\&\{.*\}\;/.test(value)) return ;

    name= name.toLowerCase() ;

    if (/^(href|src|lowsrc|dynsrc|action|background|usemap|cite|longdesc|codebase)$/i.test(name)) {
	return _proxy_jslib_full_url(value) ;
    } else if (/^on/i.test(name)) {
	return _proxy_jslib_proxify_block(value, _proxy_jslib_default_script_type, 1)
    } else if (/^style$/i.test(name)) {
	if (/(expression|function)\s*\(/i.test(value)) return ;
	else return value ;
    } else {
	return value ;
    }
}



function _proxy_jslib_proxify_block(s, type, unknown_type_ok) {
    type= type.toLowerCase() ;

    if (type=='text/css') {
	return _proxy_jslib_proxify_css(s) ;

    } else if (type.match(/^(application\/x\-javascript|application\/x\-ecmascript|application\/javascript|application\/ecmascript|text\/javascript|text\/ecmascript|text\/livescript|text\/jscript)$/)) {
	return _proxy_jslib_proxify_js(s, 1) ;

    } else {
	return unknown_type_ok ? s : '' ;
    }
}



function _proxy_jslib_proxify_css(css) {
    var out= '', m1 ;
    while (m1= css.match(/url\s*\(\s*(([^\)]*\\\))*[^\)]*)(\)|$)/i)) {
	out+= css.substr(0,m1.index) + 'url(' + _proxy_jslib_css_full_url(m1[1]) + ')' ;
	css= css.substr(m1.index+m1[0].length) ;
    }
    out+= css ;

    css= out ;
    out= '' ;
    while (m1=css.match(/\@import\s*(\"[^"]*\"|\'[^']*\'|[^\;\s\<]*)/i)) {
	if (!m1[1].match(/^url\s*\(/i)) {   // to avoid use of "(?!...)"
	    out+= css.substr(0,m1.index) + '@import ' + _proxy_jslib_css_full_url(m1[1]) ;
	} else {
	    out+= css.substr(0,m1.index) + m1[0] ;
	}
	css= css.substr(m1.index+m1[0].length) ;
    }
    out+= css ;

    return out ;
}


function _proxy_jslib_css_full_url(url) {
    var q= '' ;
    url= url.replace(/\s+$/, '') ;
    if      (url.match(/^\"/)) { q= '"' ; url= url.replace(/^\"|\"$/g, '') }
    else if (url.match(/^\'/)) { q= "'" ; url= url.replace(/^\'|\'$/g, '') }
    url= url.replace(/\\(.)/, "$1").replace(/^\s+|\s+$/g, '') ;
    url= _proxy_jslib_full_url(url) ;
    url= _proxy_jslib_global_replace(url, /([\(\)\,\s\'\"\\])/,
				     function (p) { return '\\'+p[1] } ) ;
    return q+url+q ;
}



function _proxy_jslib_return_frame_doc(enc_URL, doc) {
    var top_URL= _proxy_jslib_html_escape(_proxy_jslib_url_start_inframe
					  + _proxy_jslib_wrap_proxy_encode('x-proxy://frames/topframe?URL='
								      + encodeURIComponent(enc_URL) ) ) ;
    var page_URL= _proxy_jslib_html_escape(_proxy_jslib_url_start_inframe + enc_URL) ;
    doc.open();
    doc.write('<html>\n<frameset rows="80,*">\n'
	    + '<frame src="'+top_URL+'">\n<frame src="'+page_URL+'" name="_proxy_jslib_main_frame">\n'
	    + '</frameset>\n</html>') ;
    doc.close() ;
//alert('in return_frame_doc, after writing doc; top_URL, page_URL=\n['+top_URL+']\n['+page_URL+']') ;
}



//---- everything needed to handle proxify_js() ------------------------


function _proxy_jslib_proxify_js(s, top_level) {
    if ((s==void 0) || (s=='')) return s ;

    // hack for eval()-- return unchanged if it's not a string or String object
    if (!s.match) return s ;

    var RE ;

    if (!_proxy_jslib_RE) {  // saves time for multiple calls
	RE= new Object() ;

	// count embedded parentheses carefully when using all these in matches!
	RE.WhiteSpace= '[\x09\x0b\x0c \xa0]' ;
	RE.LineTerminator= '[\x0a\x0d]' ;

	// messy without non-greedy matching
	RE.Comment= '\\/\\*\\/*([^\\*]\\/|[^\\*\\/]*|\\**[^\\/])*\\*\\/|\\/\\/[^\\x0a\\x0d]*|\\<\\!\\-\\-[^\\x0a\\x0d]*' ;

	RE.IdentifierStart= '[a-zA-Z\\$\\_]|\\\\u[\da-fA-F]{4}' ;
	RE.IdentifierPart= RE.IdentifierStart+'|\\d' ;
	RE.IdentifierName= '('+RE.IdentifierStart+')('+RE.IdentifierPart+')*' ;

	RE.Punctuator= '\\>\\>\\>\\=?|\\=\\=\\=|\\!\\=\\=|\\<\\<\\=|\\>\\>\\=|[\\<\\>\\=\\!\\+\\*\\%\\&\\|\\^\\-]\\=|\\+\\+|\\-\\-|\\<\\<|\\>\\>|\\&\\&|\\|\\||[\\{\\}\\(\\)\\[\\]\\.\\;\\,\\<\\>\\+\\*\\%\\&\\|\\^\\!\\~\\?\\:\\=\\-]' ;
	RE.DivPunctuator= '\\/\\=?' ;

	RE.NumericLiteral= '0[xX][\\da-fA-F]+|(0|[1-9]\\d*)(\\.\\d*)?([eE][\\+\\-]?\\d+)?|\\.\\d+([eE][\\+\\-]?\\d+)?' ;
	RE.EscapeSequence= 'x[\\da-fA-F]{2}|u[\\da-fA-F]{4}|0|[^\\dxu\\x0a\\x0d]' ;
	RE.StringLiteral= '"([^\\"\\\\\\x0a\\x0d]|\\\\('+RE.EscapeSequence+'))*"|'
			+ "'([^\\'\\\\\\x0a\\x0d]|\\\\("+RE.EscapeSequence+"))*'" ;
	RE.RegularExpressionLiteral= '\\/([^\\x0a\\x0d\\*\\\\\\/]|\\\\[^\\x0a\\x0d])([^\\x0a\\x0d\\\\\\/]|\\\\[^\\x0a\\x0d])*\\/('+RE.IdentifierPart+')*' ;

	RE.Token= RE.IdentifierName+'|'+RE.NumericLiteral+'|'+RE.Punctuator+'|'+RE.StringLiteral ;
	RE.InputElementDiv= '^(('+RE.Token+'|'+RE.DivPunctuator+'|'+RE.RegularExpressionLiteral+')|'+
			    RE.WhiteSpace+'+|'+RE.LineTerminator+'|'+RE.Comment+')' ;
	RE.InputElementRegExp= '^(('+RE.Token+'|'+RE.RegularExpressionLiteral+'|'+RE.DivPunctuator+')|'+
			       RE.WhiteSpace+'+|'+RE.LineTerminator+'|'+RE.Comment+')' ;
	RE.SKIP= RE.WhiteSpace+'+|'+RE.LineTerminator+'|'+RE.Comment ;
	RE.SKIP_NO_LT= RE.WhiteSpace+'+|'+RE.Comment ;

	// make RegExp objects out of the ones we'll use
	RE.InputElementDiv= new RegExp(RE.InputElementDiv) ;
	RE.InputElementRegExp= new RegExp(RE.InputElementRegExp) ;
	RE.N_S_RE= new RegExp('^('+RE.NumericLiteral+'|'+RE.StringLiteral+'|'+RE.RegularExpressionLiteral+')$') ;
	RE.DOTSKIPEND= new RegExp('\\.('+RE.SKIP+')*$') ;
	// avoid (?!...) construct for older compatibility; thus needs extra check when using it
//	RE.NEXTISASSIGN= new RegExp('^('+RE.SKIP+')*(\\>\\>\\>\\=|\\<\\<\\=|\\>\\>\\=|[\\+\\*\\/\\%\\&\\|\\^\\-]?\\=(?!\\=))') ;
	RE.NEXTISASSIGN= new RegExp('^('+RE.SKIP+')*(\\>\\>\\>\\=|\\<\\<\\=|\\>\\>\\=|[\\+\\*\\/\\%\\&\\|\\^\\-]?\\=)') ;
	RE.NEXTISINCDEC= new RegExp('^('+RE.SKIP_NO_LT+')*(\\+\\+|\\-\\-)') ;
	RE.SKIPTOPAREN= new RegExp('^(('+RE.SKIP+')*\\()') ;
	RE.SKIPTOCOLON= new RegExp('^(('+RE.SKIP+')*\\:)') ;
	RE.SKIPTOCOMMASKIP= new RegExp('^(('+RE.SKIP+')*\\,('+RE.SKIP+')*)') ;
	RE.PUNCDIVPUNC= new RegExp('^('+RE.Punctuator+'|'+RE.DivPunctuator+')$') ;
	RE.SKIPTOIDENTIFIER= new RegExp('^('+RE.SKIP+')*('+RE.IdentifierName+')') ;
	RE.SKIPTOOFRAG= new RegExp('^('+RE.SKIP+')*([\\.\\[\\(])') ;

	_proxy_jslib_RE= RE ;

    } else {
	RE= _proxy_jslib_RE ;
    }


    var out, element, token, last_token, new_last_token, newline_since_last_token, div_ok,
	term_so_far= '', prefix= '', sub_expr, op, new_val, cur_val_str, inc_by,
	does_write, in_braces= 0, in_func= false, expr,
	var_decl, varname, eq, value, skip1, skip2, funcname,
	match, m2, o_p, o, p ;
    out= new Array() ;

    // s is constantly trimmed from the front
  OUTER:
    while(1) {
	if (div_ok) {
	    if (!(match= s.match(RE.InputElementDiv))) break ;
	} else {
	    if (!(match= s.match(RE.InputElementRegExp))) break ;
	}
	s= s.slice(match.index+match[0].length) ;

	element= match[0] ;
	token= match[2] ;

	if (token) {
	    div_ok= /^(\)|\]|\+\+|\-\-)$|^([a-zA-Z\$\_\\\d'"]|\.\d|\/..)/.test(token) ;
	    if (/^(case|delete|do|else|in|instanceof|new|return|throw|typeof|void)$/.test(token)) div_ok= false ;
	}

	if (/^[\x0a\x0d]$/.test(element)) newline_since_last_token= true ;
	new_last_token= '' ;

	if (token=='{') {
	    in_braces++ ;
	} else if (token=='}') {
	    if (--in_braces==0) in_func= false ;
	}

	if (!token) {
	    if (term_so_far) term_so_far+= element ;
	    else out[out.length]= element ;

	} else if (match= token.match(/^\_proxy(\d*)(\_jslib\_.*)/))   {
	    term_so_far+= '_proxy'+(match[1]+1)+match[2] ;

	} else if (RE.N_S_RE.test(token)) {
	    out[out.length]= prefix + term_so_far ;
	    prefix= '' ;
	    term_so_far= token ;


	} else if (/^(\+\+|\-\-|delete)$/.test(token)) {
	    if (token=='--' && (match= s.match(/^\s*\>/))) {
		s= s.slice(match.index+match[0].length) ;
		out[out.length]= prefix + term_so_far + '-->' ;
		prefix= term_so_far= '' ;
	    } else if (term_so_far!='' && !newline_since_last_token) {
		out[out.length]= prefix + term_so_far + token ;
		prefix= term_so_far= '' ;
	    } else {
		out[out.length]= prefix + term_so_far ;
		prefix= term_so_far= '' ;
		o_p= _proxy_jslib_get_next_js_term() ;
		o= o_p[0] ;
		p= o_p[1] ;
		if (p==void 0) break ;
		if (o!='') {
		    out[out.length]= "_proxy_jslib_assign('" + token + "', ("
				   + _proxy_jslib_proxify_js(o) + "), ("
				   + _proxy_jslib_proxify_js(p) + "), '')" ;
		} else {
		    p= p.match(/^'(.*)'$/)[1] ;
		    out[out.length]= "(" + p + "= _proxy_jslib_assign_rval('"
				   + token + "', '" + p + "', '', '', " + p + "))" ;
		}
	    }


	} else if (token=='eval' && (match= s.match(RE.SKIPTOPAREN))) {
	    s= s.slice(match.index+match[0].length) ;
	    _proxy_jslib_needs_jslib= true ;
	    term_so_far+= token + match[1] + '_proxy_jslib_proxify_js('
			+ _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr(1))
			+ '))' ;
	    if (!(match= s.match(/^\)/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    div_ok= true ;


	} else if (/^(open|write|writeln|close|replace|load|eval|setInterval|setTimeout|toString|src|href|background|lowsrc|action|location|useMap|longDesc|cite|codeBase|profile|cssText|insertRule|setStringValue|setProperty|backgroundImage|content|cursor|listStyleImage|host|hostname|pathname|port|protocol|search|insertNode|surroundContents|setNamedItem|getElementById|getElementsByTagName|innerHTML|outerHTML|outerText|insertAdjacentHTML|setAttribute|setAttributeNode|appendChild|insertBefore|replaceChild|nodeValue|value|cookie|domain|frames|parent|top|opener|execScript|navigate|showModalDialog|showModelessDialog)$/.test(token)) {
	    _proxy_jslib_needs_jslib= true ;
	    does_write= does_write || (token=='write') || (token=='writeln') || (token=='eval') ;
	    if ( newline_since_last_token
		 &&   /^(\)|\]|\+\+|\-\-)$|^([a-zA-Z\$\_\\\d'"]|\.\d|\/..)/.test(last_token)
		 && ! /^(case|delete|do|else|in|instanceof|new|typeof|void|function|var)$/.test(last_token) )
	    {
		out[out.length]= prefix + term_so_far ;
		prefix= term_so_far= '' ;
	    }
	    term_so_far= term_so_far.replace(RE.DOTSKIPEND, '') ;

	    if (/^[\{\,]/.test(last_token) && (match= s.match(RE.SKIPTOCOLON))) {
		s= s.slice(match.index+match[0].length) ;
		out[out.length]= prefix + term_so_far + token + match[1] ;
		prefix= term_so_far= '' ;
		new_last_token= ':' ;
		div_ok= false ;

	    } else if (prefix!='') {
		if (term_so_far=='') {
		    out[out.length]= token+"= _proxy_jslib_assign_rval('"+prefix+"', '"+token+"', '"+op+"', '', "+token+")" ;
		} else {
		    term_so_far= "_proxy_jslib_assign('"+prefix+"', "+term_so_far+", '"+token+"', '', '')" ;
		}
		prefix= '' ;
		new_last_token= ')' ;
		div_ok= true ;
	    } else if (match= s.match(RE.NEXTISINCDEC)) {
		s= s.slice(match.index+match[0].length) ;
		op= match[3] ;
		if (term_so_far=='') {
		    out[out.length]= token+"= _proxy_jslib_assign_rval('', '"+token+"', '"+op+"', '', "+token+")" ;
		} else {
		    term_so_far= "_proxy_jslib_assign('', "+term_so_far+", '"+token+"', '"+op+"', '')" ;
		}
		new_last_token= ')' ;
		div_ok= true ;
	    // use charAt() to avoid use of (?!...) construct in regex
	    } else if ((match= s.match(RE.NEXTISASSIGN)) && (s.charAt(match[0].length)!='=')) {
		s= s.slice(match.index+match[0].length) ;
		op= match[3] ;
		new_val= _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr()) ;
		if (term_so_far=='') {
		    out[out.length]= token+"= _proxy_jslib_assign_rval('', '"+token+"', '"+op+"', ("+new_val+"), "+token+")" ;
		} else {
		    term_so_far= "_proxy_jslib_assign('', "+term_so_far+", '"+token+"', '"+op+"', ("+new_val+"))" ;
		}
		new_last_token= ')' ;
		div_ok= false ;
	    } else {
		cur_val_str= (term_so_far=='')  ? (', '+token)  : '' ;
		if (term_so_far=='') term_so_far= 'null' ;
		term_so_far= "_proxy_jslib_handle("+term_so_far+", '"+token+"'"+cur_val_str+")" ;
		new_last_token= ')' ;
		div_ok= true ;
	    }


	// Skip these for the JS version-- they require %IN_CUSTOM_INSERTION
	//   etc. and would be rare anyway.  Revisit later if needed.
	//} else if (/^(applets|embeds|forms|ids|layers|anchors|images|links)$/.test(token)) {

	} else if (/^(if|while|for|with|switch)$/.test(token)) {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;
	    if (!(match= s.match(RE.SKIPTOPAREN))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    out[out.length]= match[1] + _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr(1)) + ')' ;
	    if (!(match= s.match(/^\)/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    div_ok= false ;

	} else if (token=='catch') {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;
	    if (!(match= s.match(RE.SKIPTOPAREN))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    out[out.length]= match[1] + _proxy_jslib_get_next_js_expr(1) + ')' ;
	    if (!(match= s.match(/^\)/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    div_ok= false ;

	} else if (token=='function') {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;
	    //if (!(match= s.match('^(('+RE.SKIP+')*)('+RE.IdentifierName+')?(('+RE.SKIP+')*\\()')))
	    if (!(match= s.match('^(('+RE.SKIP+')*)('+RE.IdentifierName+'(\\.('+RE.IdentifierName+'))*)?(('+RE.SKIP+')*\\()')))
		break ;
	    s= s.slice(match.index+match[0].length) ;
	    //skip1= match[1] ; funcname= match[4] ; skip2= match[7] ;
	    skip1= match[1] ; funcname= match[4] || '' ; skip2= match[11] ;
	    if (m2= funcname.match(/^_proxy(\d*)_jslib_/))
		funcname= '_proxy' + (m2[1]+1) + funcname.replace(/^_proxy(\d*)/, '') ;
	    out[out.length]= skip1 + funcname + skip2 + _proxy_jslib_get_next_js_expr(1) + ') {' ;
	    if (!(match= s.match(/^\)\s*\{/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    in_braces++ ;
	    in_func= true ;
	    div_ok= false ;

	} else if (token=='var') {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;
	    while (1) {
		var_decl= _proxy_jslib_get_next_js_expr(0) ;
		if (!(match= var_decl.match('^(('+RE.SKIP+')*)('+RE.IdentifierName+'('+RE.SKIP+')*)(\\=|in)?((x|[^x])*)$')))
		    break OUTER ;
		skip1= match[1] ; varname= match[4] ; eq= match[9] ; value= match[10] ;
		if (m2= varname.match(/^_proxy(\d*)_jslib_/))
		    varname= '_proxy' + (m2[1]+1) + varname.replace(/^_proxy(\d*)/, '') ;
		out[out.length]= skip1 + varname ;
		if (eq) out[out.length]= eq + _proxy_jslib_proxify_js(value) ;
		if (!(match= s.match(/^\,/))) break ;
		s= s.slice(match.index+match[0].length) ;
		out[out.length]= ',' ;
	    }
	    div_ok= false ;

	} else if (token=='new') {
	    out[out.length]= prefix + term_so_far ;
	    prefix= term_so_far= '' ;
	    if (match= s.match('^('+RE.SKIP+')*Function\\b')) {
		s= s.slice(match.index+match[0].length) ;
		out[out.length]= '_proxy_jslib_new_function' ;
	    } else {
		out[out.length]= token ;
	    }

	} else if ((token=='return') && !in_func && top_level && does_write) {
	    out[out.length]= prefix + term_so_far ;
	    prefix= term_so_far= '' ;
	    _proxy_jslib_needs_jslib= true ;
	    expr= _proxy_jslib_get_next_js_expr(0) ;
	    while (match=s.match(RE.SKIPTOCOMMASKIP)) {
		s= s.slice(match.index+match[0].length) ;
		expr+= ', ' + _proxy_jslib_get_next_js_expr(0) ;
	    }
	    expr= _proxy_jslib_proxify_js(expr) ;
	    out[out.length]= 'return ((_proxy_jslib_ret= ('+expr+')), _proxy_jslib_flush_write_buffers(), _proxy_jslib_ret)' ;
	    div_ok= false ;


	} else if (/^(abstract|boolean|break|byte|case|char|class|const|continue|debugger|default|delete|do|else|enum|export|extends|final|finally|float|goto|implements|in|instanceof|int|interface|long|native|package|private|protected|return|short|static|synchronized|throw|throws|transient|try|typeof|void|volatile)$/.test(token)) {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;

	} else if (token.match('^('+RE.IdentifierName+')$')) {
	    if ( newline_since_last_token
		 &&   /^(\)|\]|\+\+|\-\-)$|^([a-zA-Z\$\_\\\d'"]|\.\d|\/..)/.test(last_token)
		 && ! /^(case|delete|do|else|in|instanceof|new|typeof|void|function|var)$/.test(last_token) )
	    {
		out[out.length]= prefix + term_so_far ;
		prefix= '' ;
		term_so_far= token ;
	    } else {
		term_so_far+= token ;
	    }

	} else if (token=='.') {
	    term_so_far+= '.' ;

	} else if (token=='(') {
	    does_write= 1 ;
	    term_so_far+= '(' + _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr(1)) + ')' ;
	    if (!(match= s.match(/^\)/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    new_last_token= ')' ;
	    div_ok= true ;


	} else if (token=='[') {
	    sub_expr= _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr(1)) ;
	    if (!(match= s.match(/^\]/))) break ;
	    s= s.slice(match.index+match[0].length) ;
	    if (term_so_far) {
		_proxy_jslib_needs_jslib= true ;
		new_last_token= ')' ;
		if (prefix!='') {
		    term_so_far= "_proxy_jslib_assign('"+prefix+"', "+term_so_far+", ("+sub_expr+"), '', '')" ;
		    prefix= '' ;
		    div_ok= true ;
		} else if (match= s.match(RE.NEXTISINCDEC)) {
		    s= s.slice(match.index+match[0].length) ;
		    op= match[3] ;
		    term_so_far= "_proxy_jslib_assign('', "+term_so_far+", ("+sub_expr+"), '"+op+"', '')" ;
		    div_ok= true ;
		// use charAt() to avoid use of (?!...) construct in regex
		} else if ((match= s.match(RE.NEXTISASSIGN)) && (s.charAt(match[0].length)!='=')) {
		    s= s.slice(match.index+match[0].length) ;
		    op= match[3] ;
		    new_val= _proxy_jslib_proxify_js(_proxy_jslib_get_next_js_expr()) ;
		    term_so_far= "_proxy_jslib_assign('', "+term_so_far+", ("+sub_expr+"), '"+op+"', ("+new_val+"))" ;
		    div_ok= false ;
		} else {
		    term_so_far= "_proxy_jslib_handle("+term_so_far+", ("+sub_expr+"))" ;
		    div_ok= true ;
		}
	    } else {
		term_so_far= '['+sub_expr+']' ;
		new_last_token= ']' ;
		div_ok= true ;
	    }


	} else if (RE.PUNCDIVPUNC.test(token)) {
	    out[out.length]= prefix + term_so_far + token ;
	    prefix= term_so_far= '' ;

	} else {
	    // shouldn't get here
	}

	if (token) {
	    last_token= new_last_token  ? new_last_token  : token ;
	    newline_since_last_token= false ;
	}

    }

    out[out.length]= prefix + term_so_far ;

    if (top_level && does_write) {
	out[out.length]= ' ;\n_proxy_jslib_flush_write_buffers() ;' ;
	_proxy_jslib_needs_jslib= true ;
    }

    return out.join('') ;



    // nesting the function lets us access and modify s
    function _proxy_jslib_get_next_js_expr(allow_multiple) {
	var out= new Array(), p= new Array(), element, token, div_ok, last_token ;

	while (1) {
	    if (div_ok) {
		if (!(match= s.match(RE.InputElementDiv))) break ;
	    } else {
		if (!(match= s.match(RE.InputElementRegExp))) break ;
	    }
	    s= s.slice(match.index+match[0].length) ;
	    element= match[0] ;
	    token= match[2] ;

	    if (element==';' || element==',') {
		if (!allow_multiple && p.length==0) {
		    s= element+s ;
		    return out.join('') ;
		}

	    } else if (/^[\x0a\x0d]$/.test(element)) {
		if ( !allow_multiple && p.length==0
		     &&   /^(\)|\]|\+\+|\-\-)$|^([a-zA-Z\$\_\\\d'"]|\.\d|\/..)/.test(last_token)
		     && ! /^(case|delete|do|else|in|instanceof|new|typeof|void|function|var)$/.test(last_token)
		     &&   RE.SKIPTOIDENTIFIER.test(s) )
		{
		    s= element+s ;
		    return out.join('') ;
		}

	    } else if (/^[\(\[\{\?]$/.test(element)) {
		p[p.length]= element ;

	    } else if (/^[\)\]\}\:]$/.test(element)) {
		if (p.length==0) {
		    s=element+s ;
		    return out.join('') ;
		}
		if (p.length>0 && !(element==':' && p[p.length-1]!='?')) p.length-- ;
		if (element=='}' && p.length==0 && !allow_multiple) {
		    out[out.length]= element ;
		    return out.join('') ;
		}
	    }

	    out[out.length]= element ;

	    if (token) {
		div_ok= /^(\)|\]|\+\+|\-\-)$|^([a-zA-Z\$\_\\\d'"]|\.\d|\/..)/.test(token) ;
		if (/^(case|delete|do|else|in|instanceof|new|return|throw|typeof|void)$/.test(token)) div_ok= false ;
		last_token= token ;
	    }
	}

	return p.length==0  ? out.join('')  : void 0 ;
    }



    function _proxy_jslib_get_next_js_term() {
	var o= '', p, ofrag, match, m2 ;

	if (!(match= s.match(RE.SKIPTOIDENTIFIER))) return [void 0, void 0] ;
	s= s.slice(match.index+match[0].length) ;
	p= "'" + match[3] + "'" ;
	ofrag= match[3] ;

	while (match= s.match(RE.SKIPTOOFRAG)) {
	    s= s.slice(match.index+match[0].length) ;
	    o+= ofrag ;
	    if (match[3]=='.') {
		if (!(m2= s.match(RE.SKIPTOIDENTIFIER))) return [void 0, void 0] ;
		s= s.slice(m2.index+m2[0].length) ;
		p= "'" + m2[3] + "'" ;
		ofrag= '.' + m2[3] ;
	    } else if (match[3]=='[') {
		p= _proxy_jslib_get_next_js_expr(1) ;
		ofrag= '[' + p + ']' ;
		if (!s.match(/^\]/)) return [void 0, void 0] ;
		s= s.slice(1) ;
	    } else if (match[3]=='(') {
		p= '' ;
		ofrag= '(' + _proxy_jslib_get_next_js_expr(1) + ')' ;
		if (!s.match(/^\)/)) return [void 0, void 0] ;
		s= s.slice(1) ;
	    }
	}
	return [o, p] ;
    }

}



//---- utilities -------------------------------------------------------


// Take a heuristic guess at the type of an object, based on existing properties.
// This will only detect types that are specifically handled below.  For types
//   that are not, this returns ''.
// typeof(), instanceof, o.constructor, and o.toString() each have their problems.  :P
// Properties are carefully selected to test correctly in both Mozilla and MSIE;
//   objects and properties vary slightly by browser.
// Unfortunately, Mozilla has a bug such that it dies when HTMLAreaElement.pathname
//   is accessed.  Thus, we wrap this in a try/catch block.
// This approach will probably have to be replaced at some point.
// jsm-- use browser-dependent approach.
function _proxy_jslib_object_type(o) {
    if ((o==null) || (typeof(o)!='object')) return null ;   // can't use instanceof

    try {
	if ((o.navigator!==void 0) && (o.clearInterval!==void 0) && (o.moveBy!==void 0) && (o.self===o.window))
	    return 'Window' ;
	if ((o.alinkColor!==void 0) && (o.cookie!==void 0) && (o.writeln!==void 0))
	    return 'Document' ;
	if ((o.pathname!==void 0) && (o.protocol!==void 0) && (o.target!==void 0))
	    return 'Link' ;   // must do before 'Location'
	if ((o.pathname!==void 0) && (o.protocol!==void 0)  && (o.search!==void 0))
	    return 'Location' ;
	if ((o.background!==void 0) && (o.parentLayer!==void 0) && (o.moveAbove!==void 0))
	    return 'Layer' ;
	if ((o.hspace!==void 0) && (o.src!==void 0) && (o.border!==void 0))
	    return 'Image' ;
	if ((o.action!==void 0) && (o.encoding!==void 0) && (o.submit!==void 0))
	    return 'Form' ;
	if ((o.ownerElement!==void 0) && (o.specified!==void 0))
	    return 'Attr' ;
	if ((o.nodeName!==void 0) && (o.nodeType!==void 0) && (o.nodeValue!==void 0))
	    return 'Node' ;  // must be here after descendents of Node
	if ((o.getNamedItem!==void 0) && (o.removeNamedItem!==void 0) && (o.setNamedItem!==void 0))
	    return 'NamedNodeMap' ;
	if ((o.cloneRange!==void 0) && (o.compareBoundaryPoints!==void 0) && (o.surroundContents!==void 0))
	    return 'Range' ;
	if ((o.azimuth!==void 0) && (o.backgroundAttachment!==void 0) && (o.pageBreakInside!==void 0))
	    return 'CSS2Properties' ;
	if ((o.primitiveType!==void 0) && (o.getRectValue!==void 0) && (o.getCounterValue!==void 0))
	    return 'CSSPrimitiveValue' ;
	if ((o.getPropertyCSSValue!==void 0) && (o.getPropertyPriority!==void 0) && (o.removeProperty!==void 0))
	    return 'CSSStyleDeclaration' ;
	if ((o.getAllResponseHeaders!==void 0) && (o.getResponseHeader!==void 0) && (o.setRequestHeader!==void 0))
	    return 'XMLHttpRequest' ;   // non-standard but usually supported
    } catch(e) {
//	alert(e) ;
    }

    return '' ;
}



// Using JS (not RFC) terminology, this returns:
//   protocol, authentication, host, hostname, port, pathname, search, hash
function _proxy_jslib_parse_url(URL) {
    var u= URL.match(/^([\w\+\.\-]+\:)\/\/([^\/\?\#\@]*\@)?(([^\:\/\?\#]*)(\:[^\/\?\#]*)?)([^\?\#]*)([^#]*)(.*)$/) ;
    if (u==null) return ;   // if pattern doesn't match
    for (var i= 0 ; i<u.length ; i++)  if (u[i]==void 0) u[i]= '' ;
    u[1]= u[1].toLowerCase() ;
    u[2]= u[2].replace(/\@$/, '') ;
    u[3]= u[3].toLowerCase() ;
    u[3]= u[3].replace(/\.+(:|$)/, '$1') ;  // close potential exploit
    u[4]= u[4].toLowerCase() ;
    u[4]= u[4].replace(/\.+$/, '') ;      // close potential exploit
    u[5]= u[5].replace(/^\:/, '') ;
    return u ;
}


// returns url_start (NOT including packed flags), packed flags, and decoded target URL.
function _proxy_jslib_parse_full_url(URL) {
    if (URL=='about:blank') return ['', '', 'about:blank'] ;
    if (URL.match(/^javascript\:/i)) return ['', '', URL] ;
    if (URL=='') return ['', '', ''] ;

    var cmp, path_cmp ;

    if (_proxy_jslib_PROXY_GROUP.length) {
	for (var i in _proxy_jslib_PROXY_GROUP) {
	    if (URL.substring(0,_proxy_jslib_PROXY_GROUP[i].length)==_proxy_jslib_PROXY_GROUP[i]) {
		path_cmp= URL.substring(_proxy_jslib_PROXY_GROUP[i].length).match(/\/([^\/]*)\/?(.*)/) ;
		if (path_cmp==null) alert("CGIProxy Error: Can't parse URL <"+URL+"> with PROXY_GROUP; not setting all variables correctly.") ;
		return [_proxy_jslib_PROXY_GROUP[i],
			path_cmp[1],
			_proxy_jslib_wrap_proxy_decode(path_cmp[2])] ;
	    }
	}
    }

    cmp= URL.match(/^([\w\+\.\-]+)\:\/\/([^\/\?]*)(.*)$/) ;
    path_cmp= cmp[3].match(_proxy_jslib_RE_FULL_URL) ;
    if (cmp==null || path_cmp==null) alert("CGIProxy Error: Can't parse URL <"+URL+">; not setting all variables correctly.") ;

    return [cmp[1]+"://"+cmp[2]+path_cmp[1],
	    path_cmp[2],
	    _proxy_jslib_wrap_proxy_decode(path_cmp[3])] ;
}


function _proxy_jslib_pack_flags(flags) {
    var pflags= new Array() ;
    for (var i= 0 ; i<6 ; i++) { pflags[i]= (flags[i]==1) ? '1' : '0' }
    pflags[6]= String.fromCharCode(_proxy_jslib_MIME_TYPE_ID[flags[6]]+65) ;  // only works through #26
    return pflags.join('') ;
}

function _proxy_jslib_unpack_flags(flagst) {
    var flags= flagst.split('') ;
    for (var i= 0 ; i<6 ; i++) { flags[i]= (flags[i]=='1') ? 1 : 0 }
    flags[6]= flags[6].charCodeAt(0)-65 ;     // only works through #26
    flags[6]= _proxy_jslib_ALL_TYPES[flags[6]] ;
    return flags ;
}



function _proxy_jslib_html_escape(s) {
    if (s==void 0) return '' ;
    return s.replace(/\&/g, '&amp;')
	    .replace(/\"/g, '&quot;')
	    .replace(/\</g, '&lt;')
	    .replace(/\>/g, '&gt;') ;
}

function _proxy_jslib_html_unescape(s) {
    if (s==void 0) return '' ;
    return s.replace(/\&quot\b\;?/g, '"')
	    .replace(/\&lt\b\;?/g,   '<')
	    .replace(/\&gt\b\;?/g,   '>')
	    .replace(/\&amp\b\;?/g,  '&') ;
}


// The replace() method in Netscape is broken, :( :( so we have to implement
//   our own.  The bug is that if a function is used as the replacement pattern
//   (needed for anything complex), then *any* replace() or match() (and others?)
//   within that function (or in called functions) will cause its $' to
//   be used in place of the calling replace()'s $' .  :P
// Call this function with a string, a NON-GLOBAL (!) pattern with possible
//   parentheses, and a callback function that takes one argument that is the
//   array resulting from s.match(pattern), and returns a replacement string.
// Because of how this is implemented, ^ in pattern works much like Perl's \G.
function _proxy_jslib_global_replace(s, pattern, replace_function) {
    var out= '' ;
    var m1 ;
    while ((m1=s.match(pattern))!=null) {
	out+= s.substr(0,m1.index) + replace_function(m1) ;
	s= s.substr(m1.index+m1[0].length) ;
    }
    return out+s ;
}


//----------------------------------------------------------------------


EOF
    } # end setting of $JSLIB_BODY


    print "$HTTP_1_X 200 OK\015\012",
	  "Expires: $expires_header\015\012",
	  "Date: $date_header\015\012",
	  "Content-Type: application/x-javascript\015\012",
	  "Content-Length: ", length($JSLIB_BODY), "\015\012",
	  "\015\012",
	  $JSLIB_BODY ;

    goto EXIT ;
}